@becrafter/prompt-manager 0.1.21 → 0.1.31
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/package.json +23 -23
- package/packages/resources/tools/agent-browser/README.md +640 -0
- package/packages/resources/tools/agent-browser/agent-browser.tool.js +1388 -0
- package/packages/resources/tools/thinking/README.md +324 -0
- package/packages/resources/tools/thinking/thinking.tool.js +911 -0
- package/packages/server/README.md +3 -4
- package/packages/server/api/admin.routes.js +668 -664
- package/packages/server/api/open.routes.js +68 -67
- package/packages/server/api/surge.routes.js +5 -6
- package/packages/server/api/tool.routes.js +70 -71
- package/packages/server/app.js +70 -73
- package/packages/server/configs/authors.json +40 -0
- package/packages/server/configs/models/built-in/bigmodel.yaml +6 -6
- package/packages/server/configs/models/providers.yaml +4 -4
- package/packages/server/configs/templates/built-in/general-iteration.yaml +1 -1
- package/packages/server/configs/templates/built-in/general-optimize.yaml +1 -1
- package/packages/server/configs/templates/built-in/output-format-optimize.yaml +1 -1
- package/packages/server/index.js +3 -9
- package/packages/server/mcp/heartbeat-patch.js +1 -3
- package/packages/server/mcp/mcp.server.js +64 -134
- package/packages/server/mcp/prompt.handler.js +101 -95
- package/packages/server/middlewares/auth.middleware.js +31 -31
- package/packages/server/server.js +60 -45
- package/packages/server/services/TerminalService.js +156 -70
- package/packages/server/services/WebSocketService.js +35 -34
- package/packages/server/services/author-config.service.js +199 -0
- package/packages/server/services/manager.js +66 -60
- package/packages/server/services/model.service.js +5 -9
- package/packages/server/services/optimization.service.js +25 -22
- package/packages/server/services/template.service.js +3 -8
- package/packages/server/toolm/author-sync.service.js +97 -0
- package/packages/server/toolm/index.js +1 -2
- package/packages/server/toolm/package-installer.service.js +47 -50
- package/packages/server/toolm/tool-context.service.js +64 -62
- package/packages/server/toolm/tool-dependency.service.js +28 -30
- package/packages/server/toolm/tool-description-generator-optimized.service.js +55 -55
- package/packages/server/toolm/tool-description-generator.service.js +20 -23
- package/packages/server/toolm/tool-environment.service.js +45 -44
- package/packages/server/toolm/tool-execution.service.js +49 -48
- package/packages/server/toolm/tool-loader.service.js +13 -18
- package/packages/server/toolm/tool-logger.service.js +33 -39
- package/packages/server/toolm/tool-manager.handler.js +17 -15
- package/packages/server/toolm/tool-manual-generator.service.js +107 -87
- package/packages/server/toolm/tool-mode-handlers.service.js +52 -59
- package/packages/server/toolm/tool-storage.service.js +11 -12
- package/packages/server/toolm/tool-sync.service.js +36 -39
- package/packages/server/toolm/tool-utils.js +0 -1
- package/packages/server/toolm/tool-yaml-parser.service.js +12 -11
- package/packages/server/toolm/validate-system.js +56 -84
- package/packages/server/utils/config.js +98 -13
- package/packages/server/utils/logger.js +1 -1
- package/packages/server/utils/port-checker.js +8 -8
- package/packages/server/utils/util.js +470 -467
- package/packages/resources/tools/cognitive-thinking/README.md +0 -284
- package/packages/resources/tools/cognitive-thinking/cognitive-thinking.tool.js +0 -837
- package/packages/server/mcp/sequential-thinking.handler.js +0 -318
- package/packages/server/mcp/think-plan.handler.js +0 -274
- package/packages/server/mcp/thinking-toolkit.handler.js +0 -380
- package/packages/web/0.d1c5a72339dfc32ad86a.js +0 -1
- package/packages/web/112.8807b976372b2b0541a8.js +0 -1
- package/packages/web/130.584c7e365da413f5d9be.js +0 -1
- package/packages/web/142.72c985bc29720f975cca.js +0 -1
- package/packages/web/165.a05fc53bf84d18db36b8.js +0 -2
- package/packages/web/165.a05fc53bf84d18db36b8.js.LICENSE.txt +0 -9
- package/packages/web/203.724ab9f717b80554c397.js +0 -1
- package/packages/web/241.bf941d4f02866795f64a.js +0 -1
- package/packages/web/249.54cfb224af63f5f5ec55.js +0 -1
- package/packages/web/291.6df35042f8f296fca7cd.js +0 -1
- package/packages/web/319.2fab900a31b29873f666.js +0 -1
- package/packages/web/32.c78d866281995ec33a7b.js +0 -1
- package/packages/web/325.9ca297d0f73f38468ce9.js +0 -1
- package/packages/web/366.2f9b48fdbf8eee039e57.js +0 -1
- package/packages/web/378.6be08c612cd5a3ef97dc.js +0 -1
- package/packages/web/393.7a2f817515c5e90623d7.js +0 -1
- package/packages/web/412.062df5f732d5ba203415.js +0 -1
- package/packages/web/426.08656fef4918b3fb19ad.js +0 -1
- package/packages/web/465.2be8018327130a3bd798.js +0 -1
- package/packages/web/48.8ca96fc93667a715e67a.js +0 -1
- package/packages/web/480.44c1f1a2927486ac3d4f.js +0 -1
- package/packages/web/489.e041a8d0db15dc96d607.js +0 -1
- package/packages/web/490.9ffb26c907de020d671b.js +0 -1
- package/packages/web/492.58781369e348d91fc06a.js +0 -1
- package/packages/web/495.ed63e99791a87167c6b3.js +0 -1
- package/packages/web/510.4cc07ab7d30d5c1cd17f.js +0 -1
- package/packages/web/543.3af155ed4fa237664308.js +0 -1
- package/packages/web/567.f04ab60f8e2c2fb0745a.js +0 -1
- package/packages/web/592.f3ad085fa9c1849daa06.js +0 -1
- package/packages/web/616.b03fb801b3433b17750f.js +0 -1
- package/packages/web/617.d88def54921d2c4dc44c.js +0 -1
- package/packages/web/641.d30787d674f548928261.js +0 -1
- package/packages/web/672.5269c8399fa42a5af95d.js +0 -1
- package/packages/web/731.97cab92b71811c502bda.js +0 -1
- package/packages/web/746.3947c6f0235407e420fb.js +0 -1
- package/packages/web/756.a53233b3f3913900d5ac.js +0 -1
- package/packages/web/77.68801af593a28a631fbf.js +0 -1
- package/packages/web/802.53b2bff3cf2a69f7b80c.js +0 -1
- package/packages/web/815.b6dfab82265f56c7e046.js +0 -1
- package/packages/web/821.f5a13e5c735aac244eb9.js +0 -1
- package/packages/web/846.b9bf97d5f559270675ce.js +0 -1
- package/packages/web/869.7c10403f500e6201407f.js +0 -1
- package/packages/web/885.135050364f99e6924fb5.js +0 -1
- package/packages/web/901.fd5aeb9df630609a2b43.js +0 -1
- package/packages/web/928.f67e590de3caa4daa3ae.js +0 -1
- package/packages/web/955.d833403521ba4dd567ee.js +0 -1
- package/packages/web/981.a45cb745cf424044c8c8.js +0 -1
- package/packages/web/992.645320b60c74c8787482.js +0 -1
- package/packages/web/996.ed9a963dc9e7439eca9a.js +0 -1
- package/packages/web/css/codemirror-theme_xq-light.css +0 -43
- package/packages/web/css/codemirror.css +0 -344
- package/packages/web/css/main.196f434e6a88cd448158.css +0 -7278
- package/packages/web/css/terminal-fix.css +0 -571
- package/packages/web/index.html +0 -3
- package/packages/web/main.dceff50c7307dda04873.js +0 -2
- package/packages/web/main.dceff50c7307dda04873.js.LICENSE.txt +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TerminalService - 终端服务管理类
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 提供跨平台终端会话管理,支持PTY(伪终端)和实时交互
|
|
5
5
|
* 支持Windows、macOS和Linux系统的原生终端命令
|
|
6
6
|
*/
|
|
@@ -39,8 +39,6 @@ async function tryLoadNodePty() {
|
|
|
39
39
|
// 尝试自动修复
|
|
40
40
|
try {
|
|
41
41
|
const { spawn } = await import('child_process');
|
|
42
|
-
const { promisify } = await import('util');
|
|
43
|
-
const exec = promisify(spawn);
|
|
44
42
|
|
|
45
43
|
logger.info('正在重新编译 node-pty...');
|
|
46
44
|
const rebuildProcess = spawn('npm', ['rebuild', 'node-pty'], {
|
|
@@ -49,7 +47,7 @@ async function tryLoadNodePty() {
|
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
await new Promise((resolve, reject) => {
|
|
52
|
-
rebuildProcess.on('close',
|
|
50
|
+
rebuildProcess.on('close', code => {
|
|
53
51
|
if (code === 0) {
|
|
54
52
|
resolve();
|
|
55
53
|
} else {
|
|
@@ -98,7 +96,7 @@ class TerminalSession {
|
|
|
98
96
|
this.environment = options.environment || process.env;
|
|
99
97
|
this.isActive = true;
|
|
100
98
|
this.isFallback = options.isFallback || false; // 标记是否使用回退方案
|
|
101
|
-
|
|
99
|
+
|
|
102
100
|
// 绑定PTY事件
|
|
103
101
|
this.setupPtyEvents();
|
|
104
102
|
}
|
|
@@ -125,7 +123,7 @@ class TerminalSession {
|
|
|
125
123
|
setupPtyEvents() {
|
|
126
124
|
if (!this.pty) return;
|
|
127
125
|
|
|
128
|
-
this.pty.on('data',
|
|
126
|
+
this.pty.on('data', data => {
|
|
129
127
|
this.lastActivity = new Date();
|
|
130
128
|
this.emit('data', data);
|
|
131
129
|
});
|
|
@@ -228,18 +226,18 @@ export class TerminalService {
|
|
|
228
226
|
maxSessions: 10, // 最大会话数
|
|
229
227
|
...options
|
|
230
228
|
};
|
|
231
|
-
|
|
229
|
+
|
|
232
230
|
// 定期清理非活跃会话
|
|
233
231
|
this.cleanupInterval = setInterval(() => {
|
|
234
232
|
this.cleanupInactiveSessions();
|
|
235
233
|
}, 60000); // 每分钟检查一次
|
|
236
|
-
|
|
234
|
+
|
|
237
235
|
logger.info('TerminalService initialized');
|
|
238
|
-
|
|
236
|
+
|
|
239
237
|
// 修复 node-pty 二进制文件权限
|
|
240
238
|
this.fixNodePtyPermissions();
|
|
241
239
|
}
|
|
242
|
-
|
|
240
|
+
|
|
243
241
|
/**
|
|
244
242
|
* 修复 node-pty 二进制文件权限
|
|
245
243
|
* 这是解决 posix_spawnp failed 错误的关键
|
|
@@ -248,38 +246,54 @@ export class TerminalService {
|
|
|
248
246
|
try {
|
|
249
247
|
const { execSync } = await import('child_process');
|
|
250
248
|
const platform = process.platform;
|
|
251
|
-
|
|
249
|
+
|
|
252
250
|
// 只在 Unix-like 系统上修复权限(macOS, Linux)
|
|
253
251
|
if (platform !== 'win32') {
|
|
254
252
|
logger.info('🔧 检查并修复 node-pty 二进制文件权限...');
|
|
255
|
-
|
|
253
|
+
|
|
256
254
|
// 尝试多个可能的 node-pty 路径
|
|
257
255
|
const possiblePaths = [
|
|
258
256
|
// 路径1: 在包的 node_modules 中(开发环境)
|
|
259
|
-
path.join(
|
|
257
|
+
path.join(
|
|
258
|
+
path.dirname(path.dirname(new URL(import.meta.url).pathname)),
|
|
259
|
+
'node_modules',
|
|
260
|
+
'node-pty',
|
|
261
|
+
'prebuilds'
|
|
262
|
+
),
|
|
260
263
|
// 路径2: 在根 node_modules 中(npm 安装环境)
|
|
261
264
|
path.join(process.cwd(), 'node_modules', 'node-pty', 'prebuilds'),
|
|
262
265
|
// 路径3: 相对于当前工作目录
|
|
263
|
-
path.join(
|
|
266
|
+
path.join(
|
|
267
|
+
process.cwd(),
|
|
268
|
+
'node_modules',
|
|
269
|
+
'@becrafter',
|
|
270
|
+
'prompt-manager',
|
|
271
|
+
'node_modules',
|
|
272
|
+
'node-pty',
|
|
273
|
+
'prebuilds'
|
|
274
|
+
)
|
|
264
275
|
];
|
|
265
|
-
|
|
276
|
+
|
|
266
277
|
let ptyPath = null;
|
|
267
278
|
const fs = await import('fs');
|
|
268
|
-
|
|
279
|
+
|
|
269
280
|
for (const possiblePath of possiblePaths) {
|
|
270
281
|
if (fs.existsSync(possiblePath)) {
|
|
271
282
|
ptyPath = possiblePath;
|
|
272
283
|
break;
|
|
273
284
|
}
|
|
274
285
|
}
|
|
275
|
-
|
|
286
|
+
|
|
276
287
|
if (ptyPath) {
|
|
277
288
|
try {
|
|
278
289
|
// 添加执行权限 - 使用 find 命令来处理所有平台
|
|
279
|
-
execSync(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
290
|
+
execSync(
|
|
291
|
+
`find ${ptyPath} -type f -name "*.node" -o -name "spawn-helper" | xargs chmod +x 2>/dev/null || true`,
|
|
292
|
+
{
|
|
293
|
+
stdio: 'pipe',
|
|
294
|
+
timeout: 5000
|
|
295
|
+
}
|
|
296
|
+
);
|
|
283
297
|
logger.info('✅ node-pty 权限修复完成');
|
|
284
298
|
} catch (error) {
|
|
285
299
|
// 静默失败,不影响服务启动
|
|
@@ -306,12 +320,14 @@ export class TerminalService {
|
|
|
306
320
|
|
|
307
321
|
// 检查PTY是否可用
|
|
308
322
|
if (!PTY_AVAILABLE) {
|
|
309
|
-
throw new Error(
|
|
323
|
+
throw new Error(
|
|
324
|
+
'Terminal functionality is disabled - node-pty module is not available. Run "npm rebuild node-pty" to fix this.'
|
|
325
|
+
);
|
|
310
326
|
}
|
|
311
327
|
|
|
312
328
|
const sessionId = options.id || randomUUID();
|
|
313
329
|
const sessionOptions = { ...this.defaultOptions, ...options };
|
|
314
|
-
|
|
330
|
+
|
|
315
331
|
// 检查会话数限制
|
|
316
332
|
if (this.sessions.size >= sessionOptions.maxSessions) {
|
|
317
333
|
throw new Error(`Maximum sessions limit reached: ${sessionOptions.maxSessions}`);
|
|
@@ -325,19 +341,19 @@ export class TerminalService {
|
|
|
325
341
|
try {
|
|
326
342
|
const ptyProcess = await this.createPtyProcess(sessionOptions);
|
|
327
343
|
const session = new TerminalSession(sessionId, ptyProcess, sessionOptions);
|
|
328
|
-
|
|
344
|
+
|
|
329
345
|
// 添加事件监听
|
|
330
|
-
session.on('data',
|
|
346
|
+
session.on('data', data => {
|
|
331
347
|
this.handleSessionData(sessionId, data);
|
|
332
348
|
});
|
|
333
|
-
|
|
334
|
-
session.on('exit',
|
|
349
|
+
|
|
350
|
+
session.on('exit', info => {
|
|
335
351
|
this.handleSessionExit(sessionId, info);
|
|
336
352
|
});
|
|
337
|
-
|
|
353
|
+
|
|
338
354
|
// 存储会话
|
|
339
355
|
this.sessions.set(sessionId, session);
|
|
340
|
-
|
|
356
|
+
|
|
341
357
|
logger.info(`Terminal session created: ${sessionId}`);
|
|
342
358
|
return session;
|
|
343
359
|
} catch (error) {
|
|
@@ -354,6 +370,74 @@ export class TerminalService {
|
|
|
354
370
|
const args = this.getShellArgs(shell);
|
|
355
371
|
const cwd = options.workingDirectory || os.homedir();
|
|
356
372
|
|
|
373
|
+
// 构建优化的 PATH 环境变量
|
|
374
|
+
const buildOptimizedPath = () => {
|
|
375
|
+
const platform = process.platform;
|
|
376
|
+
const existingPaths = process.env.PATH ? process.env.PATH.split(':') : [];
|
|
377
|
+
const pathSet = new Set(existingPaths);
|
|
378
|
+
|
|
379
|
+
// 添加常见的系统工具路径(按优先级排序)
|
|
380
|
+
const systemPaths = [];
|
|
381
|
+
|
|
382
|
+
if (platform === 'darwin') {
|
|
383
|
+
// macOS 特定路径
|
|
384
|
+
systemPaths.push(
|
|
385
|
+
'/opt/homebrew/bin', // Apple Silicon Homebrew
|
|
386
|
+
'/opt/homebrew/sbin',
|
|
387
|
+
'/usr/local/bin', // Intel Homebrew
|
|
388
|
+
'/usr/local/sbin',
|
|
389
|
+
'/usr/bin',
|
|
390
|
+
'/usr/sbin',
|
|
391
|
+
'/bin',
|
|
392
|
+
'/sbin',
|
|
393
|
+
'/Library/Apple/usr/bin', // Apple 系统工具
|
|
394
|
+
'/usr/local/MacGPG2/bin', // GPG 工具
|
|
395
|
+
'~/.opencode/bin',
|
|
396
|
+
'~/.local/bin', // 用户本地工具
|
|
397
|
+
'~/bin' // 用户 bin 目录
|
|
398
|
+
);
|
|
399
|
+
} else if (platform === 'linux') {
|
|
400
|
+
// Linux 特定路径
|
|
401
|
+
systemPaths.push(
|
|
402
|
+
'/usr/local/bin',
|
|
403
|
+
'/usr/local/sbin',
|
|
404
|
+
'/usr/bin',
|
|
405
|
+
'/usr/sbin',
|
|
406
|
+
'/bin',
|
|
407
|
+
'/sbin',
|
|
408
|
+
'/snap/bin', // Snap 应用
|
|
409
|
+
'~/.local/bin',
|
|
410
|
+
'~/bin'
|
|
411
|
+
);
|
|
412
|
+
} else if (platform === 'win32') {
|
|
413
|
+
// Windows 特定路径(使用分号分隔)
|
|
414
|
+
const winPaths = [
|
|
415
|
+
`${process.env.ProgramFiles}\\Git\\bin`,
|
|
416
|
+
`${process.env.ProgramFiles}\\Git\\usr\\bin`,
|
|
417
|
+
`${process.env.ProgramFiles}\\nodejs`,
|
|
418
|
+
`${process.env['ProgramFiles(x86)']}\\Git\\bin`,
|
|
419
|
+
`${process.env['ProgramFiles(x86)']}\\Git\\usr\\bin`,
|
|
420
|
+
`${process.env['ProgramFiles(x86)']}\\nodejs`,
|
|
421
|
+
`${process.env.LOCALAPPDATA}\\Programs\\Git\\bin`,
|
|
422
|
+
`${process.env.APPDATA}\\npm`,
|
|
423
|
+
`${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code\\bin`,
|
|
424
|
+
`${process.env.ProgramFiles}\\Microsoft VS Code\\bin`
|
|
425
|
+
].filter(Boolean);
|
|
426
|
+
return `${winPaths.join(';')};${process.env.PATH || ''}`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 添加路径到集合(保持优先级顺序)
|
|
430
|
+
systemPaths.forEach(path => {
|
|
431
|
+
if (!pathSet.has(path)) {
|
|
432
|
+
pathSet.add(path);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// 合并所有路径,保持原有 PATH 的顺序,但优先添加系统路径
|
|
437
|
+
const optimizedPaths = [...pathSet];
|
|
438
|
+
return optimizedPaths.join(':');
|
|
439
|
+
};
|
|
440
|
+
|
|
357
441
|
// 确保环境变量正确,特别是 PATH
|
|
358
442
|
const env = {
|
|
359
443
|
...process.env,
|
|
@@ -365,11 +449,14 @@ export class TerminalService {
|
|
|
365
449
|
LC_ALL: process.env.LC_ALL || process.env.LANG || 'en_US.UTF-8',
|
|
366
450
|
LC_CTYPE: process.env.LC_CTYPE || process.env.LANG || 'en_US.UTF-8',
|
|
367
451
|
// 确保 TERM 变量设置
|
|
368
|
-
TERM: process.env.TERM || 'xterm-256color'
|
|
452
|
+
TERM: process.env.TERM || 'xterm-256color',
|
|
453
|
+
// 设置优化的 PATH
|
|
454
|
+
PATH: buildOptimizedPath()
|
|
369
455
|
};
|
|
370
456
|
|
|
371
457
|
logger.info(`🔧 创建终端会话 - Shell: ${shell}, Args: [${args.join(', ')}], CWD: ${cwd}`);
|
|
372
458
|
logger.info(`🔧 环境变量 - SHELL: ${env.SHELL}, TERM: ${env.TERM}, LANG: ${env.LANG}`);
|
|
459
|
+
logger.info(`🔧 PATH 已优化,包含 ${env.PATH.split(':').length} 个路径`);
|
|
373
460
|
logger.info(`🔧 Shell 可执行性检查: ${shell} ${args.join(' ')}`);
|
|
374
461
|
|
|
375
462
|
// 检查 shell 是否可执行
|
|
@@ -390,27 +477,27 @@ export class TerminalService {
|
|
|
390
477
|
|
|
391
478
|
logger.info(`✅ Shell 验证通过: ${shell}`);
|
|
392
479
|
} catch (error) {
|
|
393
|
-
logger.error(
|
|
480
|
+
logger.error('❌ Shell 验证失败:', error.message);
|
|
394
481
|
throw error;
|
|
395
482
|
}
|
|
396
483
|
|
|
397
484
|
// 创建 PTY 进程 - 使用多级回退机制
|
|
398
|
-
logger.info(
|
|
485
|
+
logger.info('🔧 尝试创建 PTY 进程...');
|
|
399
486
|
|
|
400
487
|
// 定义尝试策略的优先级
|
|
401
488
|
const strategies = [
|
|
402
489
|
// 策略 1: 使用用户指定的 shell 和 xterm-256color
|
|
403
490
|
{
|
|
404
491
|
name: 'User shell with xterm-256color',
|
|
405
|
-
shell
|
|
406
|
-
args
|
|
492
|
+
shell,
|
|
493
|
+
args,
|
|
407
494
|
term: 'xterm-256color'
|
|
408
495
|
},
|
|
409
496
|
// 策略 2: 使用用户指定的 shell 和 xterm
|
|
410
497
|
{
|
|
411
498
|
name: 'User shell with xterm',
|
|
412
|
-
shell
|
|
413
|
-
args
|
|
499
|
+
shell,
|
|
500
|
+
args,
|
|
414
501
|
term: 'xterm'
|
|
415
502
|
},
|
|
416
503
|
// 策略 3: 使用 /bin/sh 和 xterm-256color
|
|
@@ -440,15 +527,15 @@ export class TerminalService {
|
|
|
440
527
|
|
|
441
528
|
for (let i = 0; i < strategies.length; i++) {
|
|
442
529
|
const strategy = strategies[i];
|
|
443
|
-
|
|
530
|
+
|
|
444
531
|
try {
|
|
445
532
|
logger.info(`🔄 尝试策略 ${i + 1}/${strategies.length}: ${strategy.name}`);
|
|
446
|
-
|
|
533
|
+
|
|
447
534
|
const ptyProcess = pty.default.spawn(strategy.shell, strategy.args, {
|
|
448
535
|
name: strategy.term,
|
|
449
536
|
cols: options.size.cols,
|
|
450
537
|
rows: options.size.rows,
|
|
451
|
-
cwd
|
|
538
|
+
cwd,
|
|
452
539
|
env: {
|
|
453
540
|
...env,
|
|
454
541
|
TERM: strategy.term,
|
|
@@ -458,23 +545,22 @@ export class TerminalService {
|
|
|
458
545
|
|
|
459
546
|
logger.info(`✅ PTY 进程创建成功,PID: ${ptyProcess.pid}`);
|
|
460
547
|
logger.info(`✅ 使用策略: ${strategy.name}`);
|
|
461
|
-
|
|
548
|
+
|
|
462
549
|
// 更新会话选项以反映实际使用的 shell
|
|
463
550
|
options.shell = strategy.shell;
|
|
464
|
-
|
|
465
|
-
return ptyProcess;
|
|
466
551
|
|
|
552
|
+
return ptyProcess;
|
|
467
553
|
} catch (error) {
|
|
468
554
|
lastError = error;
|
|
469
555
|
logger.warn(`❌ 策略 ${i + 1} 失败: ${error.message}`);
|
|
470
|
-
|
|
556
|
+
|
|
471
557
|
// 继续尝试下一个策略
|
|
472
558
|
continue;
|
|
473
559
|
}
|
|
474
560
|
}
|
|
475
561
|
|
|
476
562
|
// 所有策略都失败了
|
|
477
|
-
logger.error(
|
|
563
|
+
logger.error('❌ 所有 PTY 创建策略都失败了');
|
|
478
564
|
logger.error(`❌ 最后一个错误: ${lastError?.message}`);
|
|
479
565
|
logger.error(`❌ 系统信息 - 平台: ${process.platform}, Node: ${process.version}`);
|
|
480
566
|
logger.error(`❌ 环境信息 - SHELL: ${env.SHELL}, TERM: ${env.TERM}`);
|
|
@@ -483,14 +569,14 @@ export class TerminalService {
|
|
|
483
569
|
|
|
484
570
|
// 提供用户友好的错误信息
|
|
485
571
|
const error = new Error(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
572
|
+
'终端创建失败:所有 PTY 创建策略都失败了。\n' +
|
|
573
|
+
`最后一个错误: ${lastError?.message}\n` +
|
|
574
|
+
'建议解决方案:\n' +
|
|
575
|
+
'1. 运行: npm rebuild node-pty\n' +
|
|
576
|
+
'2. 检查系统权限和 macOS 安全设置\n' +
|
|
577
|
+
`3. 确认 shell 路径正确: ${shell}\n` +
|
|
578
|
+
'4. 重启系统后再试\n' +
|
|
579
|
+
'5. 检查是否有其他进程占用了 PTY 资源'
|
|
494
580
|
);
|
|
495
581
|
error.code = 'TERMINAL_CREATION_FAILED';
|
|
496
582
|
error.originalError = lastError;
|
|
@@ -585,11 +671,11 @@ export class TerminalService {
|
|
|
585
671
|
cleanupInactiveSessions() {
|
|
586
672
|
const now = new Date();
|
|
587
673
|
const timeoutMs = this.defaultOptions.timeout;
|
|
588
|
-
|
|
589
|
-
for (const
|
|
590
|
-
if (!session.isActive ||
|
|
591
|
-
logger.info(`Cleaning up inactive session: ${sessionId}`);
|
|
592
|
-
this.removeSession(sessionId);
|
|
674
|
+
|
|
675
|
+
for (const session of this.sessions.values()) {
|
|
676
|
+
if (!session.isActive || now - session.lastActivity > timeoutMs) {
|
|
677
|
+
logger.info(`Cleaning up inactive session: ${session.sessionId}`);
|
|
678
|
+
this.removeSession(session.sessionId);
|
|
593
679
|
}
|
|
594
680
|
}
|
|
595
681
|
}
|
|
@@ -602,9 +688,9 @@ export class TerminalService {
|
|
|
602
688
|
const shell = this.getDefaultShellForPlatform();
|
|
603
689
|
const args = process.platform === 'win32' ? ['/c', command] : ['-c', command];
|
|
604
690
|
const cwd = options.workingDirectory || this.defaultOptions.workingDirectory;
|
|
605
|
-
|
|
691
|
+
|
|
606
692
|
const child = spawn(shell, args, {
|
|
607
|
-
cwd
|
|
693
|
+
cwd,
|
|
608
694
|
env: { ...process.env, ...options.environment },
|
|
609
695
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
610
696
|
});
|
|
@@ -612,23 +698,23 @@ export class TerminalService {
|
|
|
612
698
|
let stdout = '';
|
|
613
699
|
let stderr = '';
|
|
614
700
|
|
|
615
|
-
child.stdout?.on('data',
|
|
701
|
+
child.stdout?.on('data', data => {
|
|
616
702
|
stdout += data.toString();
|
|
617
703
|
});
|
|
618
704
|
|
|
619
|
-
child.stderr?.on('data',
|
|
705
|
+
child.stderr?.on('data', data => {
|
|
620
706
|
stderr += data.toString();
|
|
621
707
|
});
|
|
622
708
|
|
|
623
|
-
child.on('close',
|
|
709
|
+
child.on('close', code => {
|
|
624
710
|
resolve({
|
|
625
711
|
exitCode: code,
|
|
626
|
-
stdout
|
|
627
|
-
stderr
|
|
712
|
+
stdout,
|
|
713
|
+
stderr
|
|
628
714
|
});
|
|
629
715
|
});
|
|
630
716
|
|
|
631
|
-
child.on('error',
|
|
717
|
+
child.on('error', error => {
|
|
632
718
|
reject(error);
|
|
633
719
|
});
|
|
634
720
|
|
|
@@ -650,7 +736,7 @@ export class TerminalService {
|
|
|
650
736
|
if (session) {
|
|
651
737
|
return session.workingDirectory;
|
|
652
738
|
}
|
|
653
|
-
|
|
739
|
+
|
|
654
740
|
// 如果没有会话,返回默认工作目录
|
|
655
741
|
return this.defaultOptions.workingDirectory;
|
|
656
742
|
}
|
|
@@ -689,16 +775,16 @@ export class TerminalService {
|
|
|
689
775
|
*/
|
|
690
776
|
async shutdown() {
|
|
691
777
|
// 清理所有会话
|
|
692
|
-
for (const
|
|
778
|
+
for (const session of this.sessions.values()) {
|
|
693
779
|
session.terminate();
|
|
694
780
|
}
|
|
695
781
|
this.sessions.clear();
|
|
696
|
-
|
|
782
|
+
|
|
697
783
|
// 清理定时器
|
|
698
784
|
if (this.cleanupInterval) {
|
|
699
785
|
clearInterval(this.cleanupInterval);
|
|
700
786
|
}
|
|
701
|
-
|
|
787
|
+
|
|
702
788
|
logger.info('TerminalService shutdown');
|
|
703
789
|
}
|
|
704
790
|
}
|
|
@@ -729,4 +815,4 @@ export async function reloadPty() {
|
|
|
729
815
|
export const terminalService = new TerminalService();
|
|
730
816
|
|
|
731
817
|
// 导出类型定义
|
|
732
|
-
export { TerminalSession };
|
|
818
|
+
export { TerminalSession };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocketService - WebSocket服务管理类
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 提供实时双向通信服务,支持终端会话的实时数据传输
|
|
5
5
|
* 处理客户端连接、消息路由和会话管理
|
|
6
6
|
*/
|
|
@@ -22,7 +22,7 @@ class WebSocketConnection {
|
|
|
22
22
|
this.sessionId = null;
|
|
23
23
|
this.isAuthenticated = false;
|
|
24
24
|
this.userInfo = null;
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
this.setupWebSocketEvents();
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ class WebSocketConnection {
|
|
|
30
30
|
* 设置WebSocket事件监听
|
|
31
31
|
*/
|
|
32
32
|
setupWebSocketEvents() {
|
|
33
|
-
this.ws.on('message',
|
|
33
|
+
this.ws.on('message', data => {
|
|
34
34
|
this.handleMessage(data);
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ class WebSocketConnection {
|
|
|
38
38
|
this.handleClose(code, reason);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
this.ws.on('error',
|
|
41
|
+
this.ws.on('error', error => {
|
|
42
42
|
this.handleError(error);
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -54,9 +54,9 @@ class WebSocketConnection {
|
|
|
54
54
|
try {
|
|
55
55
|
this.lastActivity = new Date();
|
|
56
56
|
const message = JSON.parse(data.toString());
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
logger.debug(`Received message from client ${this.clientId}:`, message.type);
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
// 根据消息类型处理
|
|
61
61
|
switch (message.type) {
|
|
62
62
|
case 'terminal.create':
|
|
@@ -100,14 +100,14 @@ class WebSocketConnection {
|
|
|
100
100
|
this.sessionId = session.id;
|
|
101
101
|
|
|
102
102
|
// 监听会话数据
|
|
103
|
-
session.on('data',
|
|
103
|
+
session.on('data', data => {
|
|
104
104
|
this.send('terminal.data', {
|
|
105
105
|
sessionId: session.id,
|
|
106
|
-
data
|
|
106
|
+
data
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
session.on('exit',
|
|
110
|
+
session.on('exit', info => {
|
|
111
111
|
this.send('terminal.exit', {
|
|
112
112
|
sessionId: session.id,
|
|
113
113
|
exitCode: info.exitCode,
|
|
@@ -175,7 +175,7 @@ class WebSocketConnection {
|
|
|
175
175
|
/**
|
|
176
176
|
* 处理终端关闭
|
|
177
177
|
*/
|
|
178
|
-
async handleTerminalClose(
|
|
178
|
+
async handleTerminalClose(_message) {
|
|
179
179
|
if (this.sessionId) {
|
|
180
180
|
try {
|
|
181
181
|
await terminalService.removeSession(this.sessionId);
|
|
@@ -192,7 +192,7 @@ class WebSocketConnection {
|
|
|
192
192
|
/**
|
|
193
193
|
* 处理ping
|
|
194
194
|
*/
|
|
195
|
-
handlePing(
|
|
195
|
+
handlePing(_message) {
|
|
196
196
|
this.send('pong', {
|
|
197
197
|
timestamp: Date.now(),
|
|
198
198
|
clientId: this.clientId
|
|
@@ -204,7 +204,7 @@ class WebSocketConnection {
|
|
|
204
204
|
*/
|
|
205
205
|
handleClose(code, reason) {
|
|
206
206
|
logger.info(`Client ${this.clientId} disconnected: ${code} - ${reason}`);
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
// 清理关联的终端会话
|
|
209
209
|
if (this.sessionId) {
|
|
210
210
|
terminalService.removeSession(this.sessionId);
|
|
@@ -224,12 +224,12 @@ class WebSocketConnection {
|
|
|
224
224
|
send(type, data) {
|
|
225
225
|
if (this.ws.readyState === this.ws.OPEN) {
|
|
226
226
|
const message = {
|
|
227
|
-
type
|
|
227
|
+
type,
|
|
228
228
|
timestamp: Date.now(),
|
|
229
229
|
clientId: this.clientId,
|
|
230
230
|
...data
|
|
231
231
|
};
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
this.ws.send(JSON.stringify(message));
|
|
234
234
|
}
|
|
235
235
|
}
|
|
@@ -239,8 +239,8 @@ class WebSocketConnection {
|
|
|
239
239
|
*/
|
|
240
240
|
sendError(message, details = null) {
|
|
241
241
|
this.send('error', {
|
|
242
|
-
message
|
|
243
|
-
details
|
|
242
|
+
message,
|
|
243
|
+
details
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -274,33 +274,38 @@ class WebSocketConnection {
|
|
|
274
274
|
export class WebSocketService {
|
|
275
275
|
constructor(options = {}) {
|
|
276
276
|
this.options = {
|
|
277
|
-
port: options.port
|
|
277
|
+
port: options.port !== undefined ? options.port : 0, // 0 表示让系统自动分配端口
|
|
278
278
|
host: '0.0.0.0',
|
|
279
279
|
maxConnections: 100,
|
|
280
280
|
heartbeatInterval: 30000, // 30秒心跳
|
|
281
281
|
connectionTimeout: 300000, // 5分钟超时
|
|
282
282
|
...options
|
|
283
283
|
};
|
|
284
|
-
|
|
284
|
+
|
|
285
285
|
this.wss = null;
|
|
286
286
|
this.connections = new Map();
|
|
287
287
|
this.isRunning = false;
|
|
288
|
-
|
|
288
|
+
|
|
289
289
|
logger.info('WebSocketService initialized');
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
293
|
* 启动WebSocket服务
|
|
294
|
+
* @param {Object} options - 可选配置
|
|
295
|
+
* @param {number} options.port - WebSocket 端口(会覆盖构造函数中的配置)
|
|
294
296
|
*/
|
|
295
|
-
async start() {
|
|
297
|
+
async start(options = {}) {
|
|
296
298
|
if (this.isRunning) {
|
|
297
299
|
throw new Error('WebSocket service is already running');
|
|
298
300
|
}
|
|
299
301
|
|
|
302
|
+
// 合并传入的端口配置
|
|
303
|
+
const port = options.port || this.options.port;
|
|
304
|
+
|
|
300
305
|
return new Promise((resolve, reject) => {
|
|
301
306
|
try {
|
|
302
307
|
this.wss = new WebSocketServer({
|
|
303
|
-
port
|
|
308
|
+
port,
|
|
304
309
|
host: this.options.host,
|
|
305
310
|
maxConnections: this.options.maxConnections
|
|
306
311
|
});
|
|
@@ -309,7 +314,7 @@ export class WebSocketService {
|
|
|
309
314
|
this.handleConnection(ws, request);
|
|
310
315
|
});
|
|
311
316
|
|
|
312
|
-
this.wss.on('error',
|
|
317
|
+
this.wss.on('error', error => {
|
|
313
318
|
logger.error('WebSocket server error:', error);
|
|
314
319
|
if (!this.isRunning) {
|
|
315
320
|
reject(error);
|
|
@@ -328,7 +333,6 @@ export class WebSocketService {
|
|
|
328
333
|
|
|
329
334
|
resolve();
|
|
330
335
|
});
|
|
331
|
-
|
|
332
336
|
} catch (error) {
|
|
333
337
|
reject(error);
|
|
334
338
|
}
|
|
@@ -344,7 +348,7 @@ export class WebSocketService {
|
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
// 关闭所有连接
|
|
347
|
-
for (const
|
|
351
|
+
for (const connection of this.connections.values()) {
|
|
348
352
|
connection.close(1001, 'Server shutdown');
|
|
349
353
|
}
|
|
350
354
|
this.connections.clear();
|
|
@@ -369,7 +373,7 @@ export class WebSocketService {
|
|
|
369
373
|
*/
|
|
370
374
|
handleConnection(ws, request) {
|
|
371
375
|
const clientId = this.generateClientId();
|
|
372
|
-
|
|
376
|
+
|
|
373
377
|
// 检查连接数限制
|
|
374
378
|
if (this.connections.size >= this.options.maxConnections) {
|
|
375
379
|
ws.close(1013, 'Server overload');
|
|
@@ -379,12 +383,12 @@ export class WebSocketService {
|
|
|
379
383
|
try {
|
|
380
384
|
const connection = new WebSocketConnection(ws, clientId);
|
|
381
385
|
this.connections.set(clientId, connection);
|
|
382
|
-
|
|
386
|
+
|
|
383
387
|
logger.info(`New client connected: ${clientId} from ${request.socket.remoteAddress}`);
|
|
384
|
-
|
|
388
|
+
|
|
385
389
|
// 发送欢迎消息
|
|
386
390
|
connection.send('welcome', {
|
|
387
|
-
clientId
|
|
391
|
+
clientId,
|
|
388
392
|
serverInfo: {
|
|
389
393
|
version: '1.0.0',
|
|
390
394
|
platform: process.platform,
|
|
@@ -396,7 +400,6 @@ export class WebSocketService {
|
|
|
396
400
|
ws.on('close', () => {
|
|
397
401
|
this.connections.delete(clientId);
|
|
398
402
|
});
|
|
399
|
-
|
|
400
403
|
} catch (error) {
|
|
401
404
|
logger.error(`Failed to handle connection for client ${clientId}:`, error);
|
|
402
405
|
ws.close(1011, 'Internal error');
|
|
@@ -416,7 +419,7 @@ export class WebSocketService {
|
|
|
416
419
|
startHeartbeat() {
|
|
417
420
|
this.heartbeatInterval = setInterval(() => {
|
|
418
421
|
const now = Date.now();
|
|
419
|
-
|
|
422
|
+
|
|
420
423
|
for (const [clientId, connection] of this.connections) {
|
|
421
424
|
// 检查连接超时
|
|
422
425
|
if (now - connection.lastActivity > this.options.connectionTimeout) {
|
|
@@ -471,9 +474,7 @@ export class WebSocketService {
|
|
|
471
474
|
* 获取服务状态
|
|
472
475
|
*/
|
|
473
476
|
getStatus() {
|
|
474
|
-
const activeConnections = this.getAllConnections().filter(
|
|
475
|
-
conn => conn.ws.readyState === conn.ws.OPEN
|
|
476
|
-
);
|
|
477
|
+
const activeConnections = this.getAllConnections().filter(conn => conn.ws.readyState === conn.ws.OPEN);
|
|
477
478
|
|
|
478
479
|
return {
|
|
479
480
|
isRunning: this.isRunning,
|
|
@@ -491,4 +492,4 @@ export class WebSocketService {
|
|
|
491
492
|
export const webSocketService = new WebSocketService();
|
|
492
493
|
|
|
493
494
|
// 导出类型定义
|
|
494
|
-
export { WebSocketConnection };
|
|
495
|
+
export { WebSocketConnection };
|