@becrafter/prompt-manager 0.1.18 → 0.1.21
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/env.example +4 -8
- package/package.json +19 -9
- package/packages/resources/tools/cognitive-thinking/README.md +284 -0
- package/packages/resources/tools/cognitive-thinking/cognitive-thinking.tool.js +837 -0
- package/packages/server/api/admin.routes.js +19 -0
- package/packages/server/mcp/mcp.server.js +6 -6
- package/packages/server/server.js +6 -4
- package/packages/server/services/TerminalService.js +247 -13
- package/packages/server/services/WebSocketService.js +15 -5
- package/packages/server/toolm/tool-sync.service.js +8 -0
- package/packages/server/utils/config.js +24 -6
- package/packages/server/utils/util.js +63 -18
- package/packages/web/css/{main.3b61356b384d2f11f47f.css → main.196f434e6a88cd448158.css} +10 -0
- package/packages/web/css/terminal-fix.css +571 -0
- package/packages/web/index.html +1 -1
- package/packages/web/{main.77c2c4b553ca3fac223b.js → main.dceff50c7307dda04873.js} +2 -2
- package/app/desktop/assets/app.1.png +0 -0
- package/app/desktop/assets/app.png +0 -0
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon.png +0 -0
- package/app/desktop/assets/icons/tray.png +0 -0
- package/app/desktop/assets/templates/about.html +0 -147
- package/app/desktop/assets/tray.1.png +0 -0
- package/app/desktop/assets/tray.png +0 -0
- package/app/desktop/docs/ASSETS_PLANNING.md +0 -351
- package/app/desktop/docs/REFACTORING_SUMMARY.md +0 -205
- package/app/desktop/main.js +0 -340
- package/app/desktop/package-lock.json +0 -6912
- package/app/desktop/package.json +0 -119
- package/app/desktop/preload.js +0 -7
- package/app/desktop/src/core/error-handler.js +0 -108
- package/app/desktop/src/core/event-emitter.js +0 -84
- package/app/desktop/src/core/logger.js +0 -130
- package/app/desktop/src/core/state-manager.js +0 -125
- package/app/desktop/src/services/module-loader.js +0 -330
- package/app/desktop/src/services/runtime-manager.js +0 -398
- package/app/desktop/src/services/service-manager.js +0 -210
- package/app/desktop/src/services/update-manager.js +0 -267
- package/app/desktop/src/ui/about-dialog-manager.js +0 -208
- package/app/desktop/src/ui/admin-window-manager.js +0 -757
- package/app/desktop/src/ui/splash-manager.js +0 -253
- package/app/desktop/src/ui/tray-manager.js +0 -186
- package/app/desktop/src/utils/icon-manager.js +0 -133
- package/app/desktop/src/utils/path-utils.js +0 -58
- package/app/desktop/src/utils/resource-paths.js +0 -49
- package/app/desktop/src/utils/resource-sync.js +0 -260
- package/app/desktop/src/utils/runtime-sync.js +0 -241
- package/app/desktop/src/utils/self-check.js +0 -288
- package/app/desktop/src/utils/template-renderer.js +0 -284
- package/app/desktop/src/utils/version-utils.js +0 -59
- package/packages/server/.eslintrc.js +0 -70
- package/packages/server/.husky/pre-commit +0 -8
- package/packages/server/.husky/pre-push +0 -8
- package/packages/server/.prettierrc +0 -14
- package/packages/server/dev-server.js +0 -90
- package/packages/server/jsdoc.conf.json +0 -39
- package/packages/server/package.json +0 -85
- package/packages/server/playwright.config.js +0 -62
- package/packages/server/scripts/generate-docs.js +0 -300
- package/packages/server/tests/e2e/terminal-e2e.test.js +0 -315
- package/packages/server/tests/integration/terminal-websocket.test.js +0 -372
- package/packages/server/tests/integration/tools.test.js +0 -264
- package/packages/server/tests/setup.js +0 -45
- package/packages/server/tests/unit/TerminalService.test.js +0 -410
- package/packages/server/tests/unit/WebSocketService.test.js +0 -403
- package/packages/server/tests/unit/core.test.js +0 -94
- package/packages/server/typedoc.json +0 -52
- package/packages/server/vitest.config.js +0 -74
- /package/packages/web/{main.77c2c4b553ca3fac223b.js.LICENSE.txt → main.dceff50c7307dda04873.js.LICENSE.txt} +0 -0
|
@@ -16,6 +16,7 @@ import {adminAuthMiddleware} from '../middlewares/auth.middleware.js'
|
|
|
16
16
|
import { templateManager } from '../services/template.service.js';
|
|
17
17
|
import { modelManager } from '../services/model.service.js';
|
|
18
18
|
import { optimizationService } from '../services/optimization.service.js';
|
|
19
|
+
import { webSocketService } from '../services/WebSocketService.js';
|
|
19
20
|
|
|
20
21
|
const router = express.Router();
|
|
21
22
|
|
|
@@ -36,6 +37,24 @@ router.get('/config', (req, res) => {
|
|
|
36
37
|
});
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
// 获取公开配置端点(无需认证)
|
|
41
|
+
router.get('/config/public', (req, res) => {
|
|
42
|
+
const publicConfig = config.getPublicConfig();
|
|
43
|
+
|
|
44
|
+
// 如果 WebSocket 服务已启动,使用实际分配的端口
|
|
45
|
+
if (webSocketService.isRunning) {
|
|
46
|
+
publicConfig.websocketPort = webSocketService.getPort();
|
|
47
|
+
} else {
|
|
48
|
+
// 如果 WebSocket 服务未启动,返回 null 表示尚未确定
|
|
49
|
+
publicConfig.websocketPort = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
res.json({
|
|
53
|
+
success: true,
|
|
54
|
+
data: publicConfig
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
39
58
|
// 登录端点
|
|
40
59
|
router.post('/login', (req, res) => {
|
|
41
60
|
// 检查是否启用了管理员功能
|
|
@@ -130,12 +130,12 @@ export const getMcpServer = async () => {
|
|
|
130
130
|
return handleToolM(args);
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
133
|
+
// {
|
|
134
|
+
// name: 'thinking',
|
|
135
|
+
// description: `🧭 **智能思考工具箱**\n\n【规范名称】promptmanager_thinking\n【调用说明】在提示词中使用 promptmanager_thinking,实际调用时自动映射到 mcp__[server]__action\n\n⚠️ **务必先读说明**:每次首次使用某个 scenario 时,先发送仅包含 \`{"scenario":"..."}\` 的请求获取完整描述,确认理解适用场景与参数要求后,再携带 payload 调用。跳过此步骤易导致字段缺失或流程误用。\n\n## 核心特性\n\n- **双模式思考** - 提供顺序思考(exploratory)和思考规划(execution)两种模式\n- **智能引导** - 通过场景参数自动匹配合适的思考策略\n- **结构化流程** - 支持多轮思考追踪和分支管理\n- **错误预防** - 强制预读说明,避免参数配置错误\n- **灵活扩展** - 支持修订、分支和会话管理\n\n## 何时使用 Thinking Toolkit\n\n### 快速决策(IF-THEN 规则):\n- IF 需要探索性思考、诊断问题、发散推理 → 使用 scenario: "exploratory"\n- IF 需要结构化规划、制定执行步骤 → 使用 scenario: "execution"\n- IF 看到 scenario 参数 → 使用 thinking_toolkit 调用\n- IF 不确定场景用法 → 先用仅包含 scenario 的请求查看说明\n\n### 首次使用任何场景\n⚠️ **必须先发送仅包含 scenario 的请求** 阅读场景完整描述\n⚠️ 示例:thinking_toolkit with scenario: "exploratory" (无payload)\n\n## 如何使用 Thinking Toolkit\n\n### 模式 1:查看场景说明(首次使用)\n\n\`\`\`javascript\nmcp_mcp-router_thinking_toolkit({\n scenario: "exploratory"\n})\n\`\`\`\n\n**重要**:每次使用新场景前必须先执行此步骤,了解场景的具体参数要求和使用方法。\n\n### 模式 2:执行顺序思考\n\n\`\`\`javascript\nmcp_mcp-router_thinking_toolkit({\n scenario: "exploratory",\n payload: {\n thought: "分析性能下降的可能原因",\n totalThoughts: 5,\n nextThoughtNeeded: true\n }\n})\n\`\`\`\n\n### 模式 3:执行思考规划\n\n\`\`\`javascript\nmcp_mcp-router_thinking_toolkit({\n scenario: "execution",\n payload: {\n thought: "需要上线新版本",\n plan: "1. 备份 2. 部署 3. 验证",\n action: "先执行备份脚本",\n thoughtNumber: "TP-001"\n }\n})\n\`\`\`\n\n## 关键规则\n\n### ✅ 正确格式\n- 先发送 \`{"scenario": "..."}\` 获取场景说明\n- 根据说明确认参数要求后,再携带 payload 调用\n- scenario 必填:exploratory(顺序思考)或 execution(思考规划)\n- payload 根据场景填写对应字段\n\n### ❌ 常见错误\n- 不要跳过场景说明,直接携带 payload 调用(易导致参数错误)\n- 不要混用不同场景的参数字段\n- 不要在首次使用场景时直接执行 payload\n\n## 支持的思考场景\n\n### Exploratory(顺序思考)\n适合探索、诊断、发散推理场景,支持多轮思考追踪和分支管理。\n\n### Execution(思考规划)\n适合结构化计划制定和行动追踪,按步骤执行任务规划。\n\n更多场景正在开发中...`,
|
|
136
|
+
// inputSchema: thinkingToolkitInputSchema,
|
|
137
|
+
// handler: async (args) => handleThinkingToolkit(args)
|
|
138
|
+
// }
|
|
139
139
|
// {
|
|
140
140
|
// name: 'reload_prompts',
|
|
141
141
|
// description: 'Force a reload of all preset prompts to overwrite the cache.',
|
|
@@ -73,7 +73,8 @@ async function _handleConfig(options) {
|
|
|
73
73
|
promptManager.promptsDir = promptsDir;
|
|
74
74
|
await config.ensurePromptsDir();
|
|
75
75
|
await util.seedPromptsIfEmpty();
|
|
76
|
-
|
|
76
|
+
// 内置配置不需要同步到用户目录,直接从 packages/server/configs/ 读取
|
|
77
|
+
// await util.seedBuiltInConfigsIfEmpty();
|
|
77
78
|
await config.validate();
|
|
78
79
|
config.showConfig();
|
|
79
80
|
}
|
|
@@ -149,10 +150,11 @@ export async function startServer(options = {}) {
|
|
|
149
150
|
// 设置服务器实例
|
|
150
151
|
serverInstance = server;
|
|
151
152
|
|
|
152
|
-
// 启动WebSocket
|
|
153
|
+
// 启动WebSocket服务,传入 HTTP 服务器端口
|
|
153
154
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
await webSocketService.start({ port: config.getPort() });
|
|
156
|
+
const wsPort = webSocketService.getPort();
|
|
157
|
+
logger.info(`WebSocket服务启动成功,端口: ${wsPort}`);
|
|
156
158
|
} catch (wsError) {
|
|
157
159
|
logger.error('WebSocket服务启动失败:', wsError.message);
|
|
158
160
|
// WebSocket服务失败不影响主服务器运行
|
|
@@ -17,9 +17,10 @@ let PTY_AVAILABLE = false;
|
|
|
17
17
|
let PTY_LOAD_ERROR = null;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* 尝试加载 node-pty
|
|
20
|
+
* 尝试加载 node-pty 模块,如果失败则尝试自动修复
|
|
21
21
|
*/
|
|
22
22
|
async function tryLoadNodePty() {
|
|
23
|
+
logger.info('开始尝试加载 node-pty 模块...');
|
|
23
24
|
try {
|
|
24
25
|
const ptyModule = await import('node-pty');
|
|
25
26
|
pty = ptyModule;
|
|
@@ -30,10 +31,52 @@ async function tryLoadNodePty() {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
} catch (error) {
|
|
34
|
+
logger.info('node-pty 模块加载失败,准备自动修复...');
|
|
33
35
|
PTY_LOAD_ERROR = error;
|
|
34
36
|
PTY_AVAILABLE = false;
|
|
35
|
-
logger.warn('node-pty
|
|
36
|
-
|
|
37
|
+
logger.warn('node-pty 模块不可用,尝试自动修复...');
|
|
38
|
+
|
|
39
|
+
// 尝试自动修复
|
|
40
|
+
try {
|
|
41
|
+
const { spawn } = await import('child_process');
|
|
42
|
+
const { promisify } = await import('util');
|
|
43
|
+
const exec = promisify(spawn);
|
|
44
|
+
|
|
45
|
+
logger.info('正在重新编译 node-pty...');
|
|
46
|
+
const rebuildProcess = spawn('npm', ['rebuild', 'node-pty'], {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
cwd: process.cwd()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
rebuildProcess.on('close', (code) => {
|
|
53
|
+
if (code === 0) {
|
|
54
|
+
resolve();
|
|
55
|
+
} else {
|
|
56
|
+
reject(new Error(`npm rebuild 失败,退出码: ${code}`));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
rebuildProcess.on('error', reject);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 重新尝试加载
|
|
63
|
+
try {
|
|
64
|
+
const ptyModule = await import('node-pty');
|
|
65
|
+
pty = ptyModule;
|
|
66
|
+
if (pty && pty.default && pty.default.spawn) {
|
|
67
|
+
PTY_AVAILABLE = true;
|
|
68
|
+
logger.info('node-pty 模块修复成功!');
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
} catch (retryError) {
|
|
72
|
+
logger.error('自动修复失败,请手动运行: npm rebuild node-pty');
|
|
73
|
+
}
|
|
74
|
+
} catch (fixError) {
|
|
75
|
+
logger.error('自动修复过程失败:', fixError.message);
|
|
76
|
+
logger.error('请手动运行: npm rebuild node-pty');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.warn('终端功能将被禁用,请修复 node-pty 后再重启服务');
|
|
37
80
|
return false;
|
|
38
81
|
}
|
|
39
82
|
return false;
|
|
@@ -192,6 +235,64 @@ export class TerminalService {
|
|
|
192
235
|
}, 60000); // 每分钟检查一次
|
|
193
236
|
|
|
194
237
|
logger.info('TerminalService initialized');
|
|
238
|
+
|
|
239
|
+
// 修复 node-pty 二进制文件权限
|
|
240
|
+
this.fixNodePtyPermissions();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 修复 node-pty 二进制文件权限
|
|
245
|
+
* 这是解决 posix_spawnp failed 错误的关键
|
|
246
|
+
*/
|
|
247
|
+
async fixNodePtyPermissions() {
|
|
248
|
+
try {
|
|
249
|
+
const { execSync } = await import('child_process');
|
|
250
|
+
const platform = process.platform;
|
|
251
|
+
|
|
252
|
+
// 只在 Unix-like 系统上修复权限(macOS, Linux)
|
|
253
|
+
if (platform !== 'win32') {
|
|
254
|
+
logger.info('🔧 检查并修复 node-pty 二进制文件权限...');
|
|
255
|
+
|
|
256
|
+
// 尝试多个可能的 node-pty 路径
|
|
257
|
+
const possiblePaths = [
|
|
258
|
+
// 路径1: 在包的 node_modules 中(开发环境)
|
|
259
|
+
path.join(path.dirname(path.dirname(new URL(import.meta.url).pathname)), 'node_modules', 'node-pty', 'prebuilds'),
|
|
260
|
+
// 路径2: 在根 node_modules 中(npm 安装环境)
|
|
261
|
+
path.join(process.cwd(), 'node_modules', 'node-pty', 'prebuilds'),
|
|
262
|
+
// 路径3: 相对于当前工作目录
|
|
263
|
+
path.join(process.cwd(), 'node_modules', '@becrafter', 'prompt-manager', 'node_modules', 'node-pty', 'prebuilds')
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
let ptyPath = null;
|
|
267
|
+
const fs = await import('fs');
|
|
268
|
+
|
|
269
|
+
for (const possiblePath of possiblePaths) {
|
|
270
|
+
if (fs.existsSync(possiblePath)) {
|
|
271
|
+
ptyPath = possiblePath;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (ptyPath) {
|
|
277
|
+
try {
|
|
278
|
+
// 添加执行权限 - 使用 find 命令来处理所有平台
|
|
279
|
+
execSync(`find ${ptyPath} -type f -name "*.node" -o -name "spawn-helper" | xargs chmod +x 2>/dev/null || true`, {
|
|
280
|
+
stdio: 'pipe',
|
|
281
|
+
timeout: 5000
|
|
282
|
+
});
|
|
283
|
+
logger.info('✅ node-pty 权限修复完成');
|
|
284
|
+
} catch (error) {
|
|
285
|
+
// 静默失败,不影响服务启动
|
|
286
|
+
logger.debug('node-pty 权限修复失败:', error.message);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
logger.debug('未找到 node-pty prebuilds 目录,跳过权限修复');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// 静默失败,不影响服务启动
|
|
294
|
+
logger.debug('node-pty 权限修复失败:', error.message);
|
|
295
|
+
}
|
|
195
296
|
}
|
|
196
297
|
|
|
197
298
|
/**
|
|
@@ -252,17 +353,148 @@ export class TerminalService {
|
|
|
252
353
|
const shell = options.shell || this.getDefaultShellForPlatform();
|
|
253
354
|
const args = this.getShellArgs(shell);
|
|
254
355
|
const cwd = options.workingDirectory || os.homedir();
|
|
255
|
-
const env = { ...process.env, ...options.environment };
|
|
256
356
|
|
|
257
|
-
|
|
357
|
+
// 确保环境变量正确,特别是 PATH
|
|
358
|
+
const env = {
|
|
359
|
+
...process.env,
|
|
360
|
+
...options.environment,
|
|
361
|
+
// 确保 SHELL 环境变量正确设置
|
|
362
|
+
SHELL: shell,
|
|
363
|
+
// 确保 LANG 和 LC_* 变量设置
|
|
364
|
+
LANG: process.env.LANG || 'en_US.UTF-8',
|
|
365
|
+
LC_ALL: process.env.LC_ALL || process.env.LANG || 'en_US.UTF-8',
|
|
366
|
+
LC_CTYPE: process.env.LC_CTYPE || process.env.LANG || 'en_US.UTF-8',
|
|
367
|
+
// 确保 TERM 变量设置
|
|
368
|
+
TERM: process.env.TERM || 'xterm-256color'
|
|
369
|
+
};
|
|
258
370
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
371
|
+
logger.info(`🔧 创建终端会话 - Shell: ${shell}, Args: [${args.join(', ')}], CWD: ${cwd}`);
|
|
372
|
+
logger.info(`🔧 环境变量 - SHELL: ${env.SHELL}, TERM: ${env.TERM}, LANG: ${env.LANG}`);
|
|
373
|
+
logger.info(`🔧 Shell 可执行性检查: ${shell} ${args.join(' ')}`);
|
|
374
|
+
|
|
375
|
+
// 检查 shell 是否可执行
|
|
376
|
+
try {
|
|
377
|
+
const fs = await import('fs');
|
|
378
|
+
if (!fs.existsSync(shell)) {
|
|
379
|
+
throw new Error(`Shell 不存在: ${shell}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const stats = fs.statSync(shell);
|
|
383
|
+
if (!stats.isFile()) {
|
|
384
|
+
throw new Error(`Shell 不是文件: ${shell}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!(stats.mode & parseInt('111', 8))) {
|
|
388
|
+
throw new Error(`Shell 没有执行权限: ${shell}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
logger.info(`✅ Shell 验证通过: ${shell}`);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
logger.error(`❌ Shell 验证失败:`, error.message);
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 创建 PTY 进程 - 使用多级回退机制
|
|
398
|
+
logger.info(`🔧 尝试创建 PTY 进程...`);
|
|
399
|
+
|
|
400
|
+
// 定义尝试策略的优先级
|
|
401
|
+
const strategies = [
|
|
402
|
+
// 策略 1: 使用用户指定的 shell 和 xterm-256color
|
|
403
|
+
{
|
|
404
|
+
name: 'User shell with xterm-256color',
|
|
405
|
+
shell: shell,
|
|
406
|
+
args: args,
|
|
407
|
+
term: 'xterm-256color'
|
|
408
|
+
},
|
|
409
|
+
// 策略 2: 使用用户指定的 shell 和 xterm
|
|
410
|
+
{
|
|
411
|
+
name: 'User shell with xterm',
|
|
412
|
+
shell: shell,
|
|
413
|
+
args: args,
|
|
414
|
+
term: 'xterm'
|
|
415
|
+
},
|
|
416
|
+
// 策略 3: 使用 /bin/sh 和 xterm-256color
|
|
417
|
+
{
|
|
418
|
+
name: '/bin/sh with xterm-256color',
|
|
419
|
+
shell: '/bin/sh',
|
|
420
|
+
args: [],
|
|
421
|
+
term: 'xterm-256color'
|
|
422
|
+
},
|
|
423
|
+
// 策略 4: 使用 /bin/sh 和 xterm
|
|
424
|
+
{
|
|
425
|
+
name: '/bin/sh with xterm',
|
|
426
|
+
shell: '/bin/sh',
|
|
427
|
+
args: [],
|
|
428
|
+
term: 'xterm'
|
|
429
|
+
},
|
|
430
|
+
// 策略 5: 使用 /bin/sh 和 ansi
|
|
431
|
+
{
|
|
432
|
+
name: '/bin/sh with ansi',
|
|
433
|
+
shell: '/bin/sh',
|
|
434
|
+
args: [],
|
|
435
|
+
term: 'ansi'
|
|
436
|
+
}
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
let lastError = null;
|
|
440
|
+
|
|
441
|
+
for (let i = 0; i < strategies.length; i++) {
|
|
442
|
+
const strategy = strategies[i];
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
logger.info(`🔄 尝试策略 ${i + 1}/${strategies.length}: ${strategy.name}`);
|
|
446
|
+
|
|
447
|
+
const ptyProcess = pty.default.spawn(strategy.shell, strategy.args, {
|
|
448
|
+
name: strategy.term,
|
|
449
|
+
cols: options.size.cols,
|
|
450
|
+
rows: options.size.rows,
|
|
451
|
+
cwd: cwd,
|
|
452
|
+
env: {
|
|
453
|
+
...env,
|
|
454
|
+
TERM: strategy.term,
|
|
455
|
+
SHELL: strategy.shell
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
logger.info(`✅ PTY 进程创建成功,PID: ${ptyProcess.pid}`);
|
|
460
|
+
logger.info(`✅ 使用策略: ${strategy.name}`);
|
|
461
|
+
|
|
462
|
+
// 更新会话选项以反映实际使用的 shell
|
|
463
|
+
options.shell = strategy.shell;
|
|
464
|
+
|
|
465
|
+
return ptyProcess;
|
|
466
|
+
|
|
467
|
+
} catch (error) {
|
|
468
|
+
lastError = error;
|
|
469
|
+
logger.warn(`❌ 策略 ${i + 1} 失败: ${error.message}`);
|
|
470
|
+
|
|
471
|
+
// 继续尝试下一个策略
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 所有策略都失败了
|
|
477
|
+
logger.error(`❌ 所有 PTY 创建策略都失败了`);
|
|
478
|
+
logger.error(`❌ 最后一个错误: ${lastError?.message}`);
|
|
479
|
+
logger.error(`❌ 系统信息 - 平台: ${process.platform}, Node: ${process.version}`);
|
|
480
|
+
logger.error(`❌ 环境信息 - SHELL: ${env.SHELL}, TERM: ${env.TERM}`);
|
|
481
|
+
logger.error(`❌ 原始 Shell 路径: ${shell}, 参数: [${args.join(', ')}]`);
|
|
482
|
+
logger.error(`❌ 工作目录: ${cwd}`);
|
|
483
|
+
|
|
484
|
+
// 提供用户友好的错误信息
|
|
485
|
+
const error = new Error(
|
|
486
|
+
`终端创建失败:所有 PTY 创建策略都失败了。\n` +
|
|
487
|
+
`最后一个错误: ${lastError?.message}\n` +
|
|
488
|
+
`建议解决方案:\n` +
|
|
489
|
+
`1. 运行: npm rebuild node-pty\n` +
|
|
490
|
+
`2. 检查系统权限和 macOS 安全设置\n` +
|
|
491
|
+
`3. 确认 shell 路径正确: ${shell}\n` +
|
|
492
|
+
`4. 重启系统后再试\n` +
|
|
493
|
+
`5. 检查是否有其他进程占用了 PTY 资源`
|
|
494
|
+
);
|
|
495
|
+
error.code = 'TERMINAL_CREATION_FAILED';
|
|
496
|
+
error.originalError = lastError;
|
|
497
|
+
throw error;
|
|
266
498
|
}
|
|
267
499
|
|
|
268
500
|
/**
|
|
@@ -291,7 +523,9 @@ export class TerminalService {
|
|
|
291
523
|
}
|
|
292
524
|
return ['/c'];
|
|
293
525
|
}
|
|
294
|
-
|
|
526
|
+
// 不使用 -l 参数,避免 login shell 导致的 posix_spawnp 失败
|
|
527
|
+
// 如果需要交互式 shell,可以通过环境变量或 shell 配置来实现
|
|
528
|
+
return [];
|
|
295
529
|
}
|
|
296
530
|
|
|
297
531
|
/**
|
|
@@ -274,7 +274,7 @@ class WebSocketConnection {
|
|
|
274
274
|
export class WebSocketService {
|
|
275
275
|
constructor(options = {}) {
|
|
276
276
|
this.options = {
|
|
277
|
-
port:
|
|
277
|
+
port: options.port || 0, // 0 表示让系统自动分配端口,或使用外部指定的端口
|
|
278
278
|
host: '0.0.0.0',
|
|
279
279
|
maxConnections: 100,
|
|
280
280
|
heartbeatInterval: 30000, // 30秒心跳
|
|
@@ -318,11 +318,14 @@ export class WebSocketService {
|
|
|
318
318
|
|
|
319
319
|
this.wss.on('listening', () => {
|
|
320
320
|
this.isRunning = true;
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// 获取实际分配的端口
|
|
322
|
+
const address = this.wss.address();
|
|
323
|
+
this.actualPort = typeof address === 'string' ? parseInt(address) : address.port;
|
|
324
|
+
logger.info(`WebSocket server listening on ${this.options.host}:${this.actualPort}`);
|
|
325
|
+
|
|
323
326
|
// 启动心跳检测
|
|
324
327
|
this.startHeartbeat();
|
|
325
|
-
|
|
328
|
+
|
|
326
329
|
resolve();
|
|
327
330
|
});
|
|
328
331
|
|
|
@@ -457,6 +460,13 @@ export class WebSocketService {
|
|
|
457
460
|
return Array.from(this.connections.values());
|
|
458
461
|
}
|
|
459
462
|
|
|
463
|
+
/**
|
|
464
|
+
* 获取实际分配的端口
|
|
465
|
+
*/
|
|
466
|
+
getPort() {
|
|
467
|
+
return this.actualPort || this.options.port;
|
|
468
|
+
}
|
|
469
|
+
|
|
460
470
|
/**
|
|
461
471
|
* 获取服务状态
|
|
462
472
|
*/
|
|
@@ -467,7 +477,7 @@ export class WebSocketService {
|
|
|
467
477
|
|
|
468
478
|
return {
|
|
469
479
|
isRunning: this.isRunning,
|
|
470
|
-
port: this.
|
|
480
|
+
port: this.getPort(),
|
|
471
481
|
host: this.options.host,
|
|
472
482
|
totalConnections: this.connections.size,
|
|
473
483
|
activeConnections: activeConnections.length,
|
|
@@ -116,6 +116,14 @@ export async function syncSystemTools() {
|
|
|
116
116
|
const sandboxToolFile = path.join(sandboxDir, `${toolName}.tool.js`);
|
|
117
117
|
await fs.copyFile(toolFile, sandboxToolFile);
|
|
118
118
|
|
|
119
|
+
// 复制 README.md 文件(如果存在)
|
|
120
|
+
const readmeFile = path.join(toolDir, 'README.md');
|
|
121
|
+
if (await pathExists(readmeFile)) {
|
|
122
|
+
const sandboxReadmeFile = path.join(sandboxDir, 'README.md');
|
|
123
|
+
await fs.copyFile(readmeFile, sandboxReadmeFile);
|
|
124
|
+
logger.debug(`已复制 README.md: ${toolName}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
// 创建或更新 package.json
|
|
120
128
|
const packageJsonPath = path.join(sandboxDir, 'package.json');
|
|
121
129
|
let packageJson = {};
|
|
@@ -77,14 +77,12 @@ MCP Prompt Server - 智能 Prompt 管理服务器
|
|
|
77
77
|
-v, --version 显示版本信息
|
|
78
78
|
|
|
79
79
|
环境变量:
|
|
80
|
-
MCP_SERVER_NAME 服务器名称 (默认: prompt-manager)
|
|
81
80
|
SERVER_PORT 服务器端口 (默认: 5621)
|
|
82
81
|
PROMPTS_DIR Prompts目录路径
|
|
83
|
-
MCP_SERVER_VERSION 服务器版本
|
|
84
82
|
LOG_LEVEL 日志级别 (默认: info)
|
|
85
83
|
MAX_PROMPTS 最大prompt数量限制 (默认: 1000)
|
|
86
84
|
RECURSIVE_SCAN 是否启用递归扫描子目录 (默认: true)
|
|
87
|
-
ADMIN_ENABLE
|
|
85
|
+
ADMIN_ENABLE 是否启用管理界面 (默认: true)
|
|
88
86
|
ADMIN_REQUIRE_AUTH 是否需要登录认证 (默认: true)
|
|
89
87
|
ADMIN_USERNAME 管理员用户名 (默认: admin)
|
|
90
88
|
ADMIN_PASSWORD 管理员密码 (默认: admin)
|
|
@@ -120,8 +118,8 @@ export class Config {
|
|
|
120
118
|
this.port = cliArgs.port || process.env.SERVER_PORT || 5621;
|
|
121
119
|
|
|
122
120
|
// 其他配置
|
|
123
|
-
this.serverName =
|
|
124
|
-
this.serverVersion =
|
|
121
|
+
this.serverName = 'prompt-manager';
|
|
122
|
+
this.serverVersion = '0.1.20';
|
|
125
123
|
this.logLevel = process.env.LOG_LEVEL || 'info';
|
|
126
124
|
this.maxPrompts = parseInt(process.env.MAX_PROMPTS) || 1000;
|
|
127
125
|
this.recursiveScan = process.env.RECURSIVE_SCAN !== 'false'; // 默认启用递归扫描
|
|
@@ -310,6 +308,26 @@ export class Config {
|
|
|
310
308
|
}
|
|
311
309
|
}
|
|
312
310
|
|
|
311
|
+
/**
|
|
312
|
+
* 获取公开配置(前端可访问的配置信息)
|
|
313
|
+
*/
|
|
314
|
+
getPublicConfig() {
|
|
315
|
+
return {
|
|
316
|
+
serverName: this.serverName,
|
|
317
|
+
serverVersion: this.serverVersion,
|
|
318
|
+
port: this.port,
|
|
319
|
+
adminEnable: this.adminEnable,
|
|
320
|
+
adminPath: this.adminPath,
|
|
321
|
+
websocketPort: null, // 这个会在 admin.routes.js 中动态设置
|
|
322
|
+
features: {
|
|
323
|
+
terminal: true,
|
|
324
|
+
optimization: true,
|
|
325
|
+
templates: true,
|
|
326
|
+
models: true
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
313
331
|
/**
|
|
314
332
|
* 显示当前配置(输出到 stderr,不干扰 MCP 通信)
|
|
315
333
|
*/
|
|
@@ -322,7 +340,7 @@ export class Config {
|
|
|
322
340
|
process.stderr.write(` 递归扫描: ${this.recursiveScan ? '启用' : '禁用'}\n`);
|
|
323
341
|
process.stderr.write(` Prompts目录: ${this.promptsDir}\n`);
|
|
324
342
|
process.stderr.write(` 最大Prompt数量: ${this.maxPrompts}\n`);
|
|
325
|
-
process.stderr.write(`
|
|
343
|
+
process.stderr.write(` 管理界面: ${this.adminEnable ? '启用' : '禁用'}\n`);
|
|
326
344
|
if (this.adminEnable) {
|
|
327
345
|
process.stderr.write(` 登录认证: ${this.adminRequireAuth ? '需要' : '不需要'}\n`);
|
|
328
346
|
}
|
|
@@ -385,39 +385,84 @@ export class Util {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
getWebUiRoot() {
|
|
388
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
389
|
+
const __dirname = path.dirname(__filename);
|
|
390
|
+
|
|
391
|
+
console.log('🔍 getWebUiRoot() called from:', __dirname);
|
|
392
|
+
|
|
388
393
|
// 检查是否在 Electron 环境中运行
|
|
389
|
-
const isElectron = typeof process !== 'undefined' &&
|
|
390
|
-
process.versions &&
|
|
394
|
+
const isElectron = typeof process !== 'undefined' &&
|
|
395
|
+
process.versions &&
|
|
391
396
|
process.versions.electron;
|
|
392
|
-
|
|
397
|
+
|
|
393
398
|
// 检查是否是打包应用
|
|
394
399
|
if (isElectron && process.resourcesPath) {
|
|
395
|
-
// 检查是否在打包模式(在我们的应用目录下有 app.asar)
|
|
396
400
|
const ourAppAsar = path.join(process.resourcesPath, 'app.asar');
|
|
397
401
|
if (fs.existsSync(ourAppAsar)) {
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
const asarPath = path.join(process.resourcesPath, 'app.asar', 'web');
|
|
403
|
+
console.log('📦 Using Electron ASAR path:', asarPath);
|
|
404
|
+
return asarPath;
|
|
400
405
|
}
|
|
401
406
|
}
|
|
402
|
-
|
|
403
|
-
// 在开发环境中,web UI 位于项目目录中的 packages/web
|
|
404
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
405
|
-
const __dirname = path.dirname(__filename);
|
|
406
|
-
const devWebPath = path.join(__dirname, '..', 'web');
|
|
407
407
|
|
|
408
|
+
// 在开发环境中,web UI 位于项目目录中的 packages/web
|
|
409
|
+
const devWebPath = path.join(__dirname, '..', '..', 'web');
|
|
410
|
+
console.log('🔍 Checking dev path:', devWebPath);
|
|
408
411
|
if (this._pathExistsSync(devWebPath)) {
|
|
412
|
+
console.log('✅ Using dev environment path:', devWebPath);
|
|
409
413
|
return devWebPath;
|
|
410
414
|
}
|
|
411
415
|
|
|
412
|
-
// 在 npm
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
416
|
+
// 在 npm 包环境中,查找 packages/web
|
|
417
|
+
// 从当前文件位置向上查找可能的包根目录
|
|
418
|
+
let currentDir = __dirname;
|
|
419
|
+
let searchDepth = 0;
|
|
420
|
+
const maxDepth = 5;
|
|
421
|
+
|
|
422
|
+
while (searchDepth < maxDepth) {
|
|
423
|
+
// 检查 packages/web
|
|
424
|
+
const webPath = path.join(currentDir, 'packages', 'web');
|
|
425
|
+
console.log('🔍 Checking npm package path:', webPath);
|
|
426
|
+
if (this._pathExistsSync(webPath)) {
|
|
427
|
+
console.log('✅ Found web UI in npm package:', webPath);
|
|
428
|
+
return webPath;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 检查直接的 web 目录(某些打包场景)
|
|
432
|
+
const altWebPath = path.join(currentDir, 'web');
|
|
433
|
+
console.log('🔍 Checking alternative path:', altWebPath);
|
|
434
|
+
if (this._pathExistsSync(altWebPath)) {
|
|
435
|
+
console.log('✅ Found web UI in alternative path:', altWebPath);
|
|
436
|
+
return altWebPath;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// 向上查找
|
|
440
|
+
const parentDir = path.dirname(currentDir);
|
|
441
|
+
if (parentDir === currentDir) {
|
|
442
|
+
// 已经到达根目录
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
currentDir = parentDir;
|
|
446
|
+
searchDepth++;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 如果都找不到,使用当前工作目录作为最后尝试
|
|
450
|
+
const cwdWebPath = path.join(process.cwd(), 'packages', 'web');
|
|
451
|
+
console.log('🔍 Checking CWD path:', cwdWebPath);
|
|
452
|
+
if (this._pathExistsSync(cwdWebPath)) {
|
|
453
|
+
console.log('✅ Found web UI in CWD:', cwdWebPath);
|
|
454
|
+
return cwdWebPath;
|
|
417
455
|
}
|
|
418
456
|
|
|
419
|
-
//
|
|
420
|
-
|
|
457
|
+
// 最后的fallback
|
|
458
|
+
console.error('❌ Web UI not found in any location. This will cause blank pages.');
|
|
459
|
+
console.error('Current directory:', __dirname);
|
|
460
|
+
console.error('Working directory:', process.cwd());
|
|
461
|
+
console.error('Checked paths:');
|
|
462
|
+
console.error(' - Dev path:', devWebPath);
|
|
463
|
+
console.error(' - CWD path:', cwdWebPath);
|
|
464
|
+
|
|
465
|
+
return devWebPath; // 返回开发路径作为fallback,虽然它不存在
|
|
421
466
|
};
|
|
422
467
|
|
|
423
468
|
/**
|
|
@@ -891,6 +891,7 @@ aside {
|
|
|
891
891
|
display: flex;
|
|
892
892
|
align-items: center;
|
|
893
893
|
justify-content: center;
|
|
894
|
+
pointer-events: none;
|
|
894
895
|
}
|
|
895
896
|
|
|
896
897
|
.prompt-item:hover {
|
|
@@ -991,6 +992,7 @@ aside {
|
|
|
991
992
|
rgba(238, 238, 238, 0.6) 60%,
|
|
992
993
|
rgba(238, 238, 238, 0) 100%);
|
|
993
994
|
z-index: 1;
|
|
995
|
+
pointer-events: auto;
|
|
994
996
|
}
|
|
995
997
|
|
|
996
998
|
.prompt-item {
|
|
@@ -5323,6 +5325,10 @@ body .xterm.theme-dark .xterm-selection .xterm-char {
|
|
|
5323
5325
|
transition: all 0.3s ease;
|
|
5324
5326
|
}
|
|
5325
5327
|
|
|
5328
|
+
.recommended-prompt-modal.hidden {
|
|
5329
|
+
display: none !important;
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5326
5332
|
.recommended-prompt-modal.active {
|
|
5327
5333
|
opacity: 1;
|
|
5328
5334
|
visibility: visible;
|
|
@@ -5496,6 +5502,10 @@ body .xterm.theme-dark .xterm-selection .xterm-char {
|
|
|
5496
5502
|
transition: all 0.3s ease;
|
|
5497
5503
|
}
|
|
5498
5504
|
|
|
5505
|
+
.sync-prompt-modal.hidden {
|
|
5506
|
+
display: none !important;
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5499
5509
|
.sync-prompt-modal.active {
|
|
5500
5510
|
opacity: 1;
|
|
5501
5511
|
visibility: visible;
|