@becrafter/prompt-manager 0.1.31 → 0.2.3-alpha.7
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 +20 -4
- package/packages/resources/tools/agent-browser/agent-browser.tool.js +18 -17
- package/packages/server/api/admin.routes.js +385 -0
- package/packages/server/mcp/prompt.handler.js +6 -6
- package/packages/server/server.js +13 -0
- package/packages/server/services/TerminalService.js +37 -17
- package/packages/server/services/skill-sync.service.js +223 -0
- package/packages/server/services/skills.service.js +731 -0
- package/packages/server/utils/config.js +8 -0
- package/packages/server/utils/util.js +27 -21
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { randomUUID } from 'crypto';
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
|
+
import fs from 'fs';
|
|
11
12
|
import path from 'path';
|
|
12
13
|
import os from 'os';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
13
15
|
|
|
14
16
|
// 延迟加载 node-pty,避免编译错误
|
|
15
17
|
let pty = null;
|
|
@@ -244,22 +246,19 @@ export class TerminalService {
|
|
|
244
246
|
*/
|
|
245
247
|
async fixNodePtyPermissions() {
|
|
246
248
|
try {
|
|
247
|
-
const { execSync } = await import('child_process');
|
|
248
249
|
const platform = process.platform;
|
|
249
250
|
|
|
250
251
|
// 只在 Unix-like 系统上修复权限(macOS, Linux)
|
|
251
252
|
if (platform !== 'win32') {
|
|
252
253
|
logger.info('🔧 检查并修复 node-pty 二进制文件权限...');
|
|
253
254
|
|
|
255
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
256
|
+
const __dirname = path.dirname(__filename);
|
|
257
|
+
|
|
254
258
|
// 尝试多个可能的 node-pty 路径
|
|
255
259
|
const possiblePaths = [
|
|
256
260
|
// 路径1: 在包的 node_modules 中(开发环境)
|
|
257
|
-
path.join(
|
|
258
|
-
path.dirname(path.dirname(new URL(import.meta.url).pathname)),
|
|
259
|
-
'node_modules',
|
|
260
|
-
'node-pty',
|
|
261
|
-
'prebuilds'
|
|
262
|
-
),
|
|
261
|
+
path.join(path.dirname(__dirname), 'node_modules', 'node-pty', 'prebuilds'),
|
|
263
262
|
// 路径2: 在根 node_modules 中(npm 安装环境)
|
|
264
263
|
path.join(process.cwd(), 'node_modules', 'node-pty', 'prebuilds'),
|
|
265
264
|
// 路径3: 相对于当前工作目录
|
|
@@ -274,8 +273,16 @@ export class TerminalService {
|
|
|
274
273
|
)
|
|
275
274
|
];
|
|
276
275
|
|
|
276
|
+
// 路径4: Electron 打包环境(app.asar.unpacked / resources)
|
|
277
|
+
if (process.resourcesPath) {
|
|
278
|
+
possiblePaths.push(
|
|
279
|
+
path.join(process.resourcesPath, 'app.asar.unpacked', 'node_modules', 'node-pty', 'prebuilds'),
|
|
280
|
+
path.join(process.resourcesPath, 'app.asar.unpacked', 'app', 'node_modules', 'node-pty', 'prebuilds'),
|
|
281
|
+
path.join(process.resourcesPath, 'node_modules', 'node-pty', 'prebuilds')
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
277
285
|
let ptyPath = null;
|
|
278
|
-
const fs = await import('fs');
|
|
279
286
|
|
|
280
287
|
for (const possiblePath of possiblePaths) {
|
|
281
288
|
if (fs.existsSync(possiblePath)) {
|
|
@@ -286,14 +293,27 @@ export class TerminalService {
|
|
|
286
293
|
|
|
287
294
|
if (ptyPath) {
|
|
288
295
|
try {
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
{
|
|
293
|
-
|
|
294
|
-
|
|
296
|
+
// 递归修复 .node 与 spawn-helper 权限,避免空格路径问题
|
|
297
|
+
const fixPermissionsRecursive = async targetPath => {
|
|
298
|
+
const entries = await fs.promises.readdir(targetPath, { withFileTypes: true });
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
const entryPath = path.join(targetPath, entry.name);
|
|
301
|
+
if (entry.isDirectory()) {
|
|
302
|
+
await fixPermissionsRecursive(entryPath);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (entry.isFile() && (entry.name.endsWith('.node') || entry.name === 'spawn-helper')) {
|
|
307
|
+
try {
|
|
308
|
+
await fs.promises.chmod(entryPath, 0o755);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
logger.debug(`node-pty 权限修复失败: ${entryPath} - ${error.message}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
295
313
|
}
|
|
296
|
-
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
await fixPermissionsRecursive(ptyPath);
|
|
297
317
|
logger.info('✅ node-pty 权限修复完成');
|
|
298
318
|
} catch (error) {
|
|
299
319
|
// 静默失败,不影响服务启动
|
|
@@ -674,8 +694,8 @@ export class TerminalService {
|
|
|
674
694
|
|
|
675
695
|
for (const session of this.sessions.values()) {
|
|
676
696
|
if (!session.isActive || now - session.lastActivity > timeoutMs) {
|
|
677
|
-
logger.info(`Cleaning up inactive session: ${session.
|
|
678
|
-
this.removeSession(session.
|
|
697
|
+
logger.info(`Cleaning up inactive session: ${session.id}`);
|
|
698
|
+
this.removeSession(session.id);
|
|
679
699
|
}
|
|
680
700
|
}
|
|
681
701
|
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import pkg from 'sync-hub';
|
|
5
|
+
const { createSyncer } = pkg;
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { config } from '../utils/config.js';
|
|
8
|
+
|
|
9
|
+
function expandPath(inputPath) {
|
|
10
|
+
if (!inputPath) return inputPath;
|
|
11
|
+
if (inputPath.startsWith('~/')) {
|
|
12
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
13
|
+
}
|
|
14
|
+
return inputPath.replace(/\$(\w+)|\$\{(\w+)\}/g, (match, varName1, varName2) => {
|
|
15
|
+
const varName = varName1 || varName2;
|
|
16
|
+
return process.env[varName] || match;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 技能全局同步服务
|
|
22
|
+
* 职责:
|
|
23
|
+
* 1. 管理同步配置(目标目录、开关)
|
|
24
|
+
* 2. 监听技能目录变更并自动同步
|
|
25
|
+
* 3. 提供手动同步接口
|
|
26
|
+
*/
|
|
27
|
+
class SkillSyncService {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.configPath = path.join(config.getConfigsDir(), 'skill-sync.json');
|
|
30
|
+
this.skillsDir = config.getSkillsDir();
|
|
31
|
+
this.syncConfig = {
|
|
32
|
+
enabled: false,
|
|
33
|
+
targets: [],
|
|
34
|
+
lastSyncTime: null,
|
|
35
|
+
lastSyncMethod: null,
|
|
36
|
+
error: null
|
|
37
|
+
};
|
|
38
|
+
this.watcher = null;
|
|
39
|
+
this.syncHub = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 初始化服务
|
|
44
|
+
*/
|
|
45
|
+
async init() {
|
|
46
|
+
try {
|
|
47
|
+
await this.loadConfig();
|
|
48
|
+
// 在测试环境下默认不启动监听,除非配置显式开启
|
|
49
|
+
if (this.syncConfig.enabled && process.env.NODE_ENV !== 'test') {
|
|
50
|
+
await this.startWatching();
|
|
51
|
+
}
|
|
52
|
+
logger.info('SkillSyncService 初始化完成');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error('SkillSyncService 初始化失败:', error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 加载配置
|
|
60
|
+
*/
|
|
61
|
+
async loadConfig() {
|
|
62
|
+
try {
|
|
63
|
+
if (await fs.pathExists(this.configPath)) {
|
|
64
|
+
const data = await fs.readJson(this.configPath);
|
|
65
|
+
this.syncConfig = { ...this.syncConfig, ...data };
|
|
66
|
+
} else {
|
|
67
|
+
await this.saveConfig();
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error('加载同步配置失败:', error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 保存配置
|
|
76
|
+
*/
|
|
77
|
+
async saveConfig() {
|
|
78
|
+
try {
|
|
79
|
+
await fs.ensureDir(path.dirname(this.configPath));
|
|
80
|
+
await fs.writeJson(this.configPath, this.syncConfig, { spaces: 2 });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error('保存同步配置失败:', error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 更新配置
|
|
88
|
+
* @param {Object} newConfig - 新配置项
|
|
89
|
+
*/
|
|
90
|
+
async updateConfig(newConfig) {
|
|
91
|
+
const wasEnabled = this.syncConfig.enabled;
|
|
92
|
+
this.syncConfig = { ...this.syncConfig, ...newConfig };
|
|
93
|
+
await this.saveConfig();
|
|
94
|
+
|
|
95
|
+
if (this.syncConfig.enabled && !wasEnabled) {
|
|
96
|
+
await this.startWatching();
|
|
97
|
+
} else if (!this.syncConfig.enabled && wasEnabled) {
|
|
98
|
+
await this.stopWatching();
|
|
99
|
+
} else if (this.syncConfig.enabled && wasEnabled) {
|
|
100
|
+
// 如果目标目录改变,重启监听以确保同步到新位置
|
|
101
|
+
await this.stopWatching();
|
|
102
|
+
await this.startWatching();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取配置
|
|
108
|
+
*/
|
|
109
|
+
getConfig() {
|
|
110
|
+
return {
|
|
111
|
+
...this.syncConfig,
|
|
112
|
+
effectiveSyncMethod: this.detectSyncMethod()
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getResolvedTargets() {
|
|
117
|
+
return (this.syncConfig.targets || []).map(target => expandPath(target)).filter(Boolean);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
detectSyncMethod() {
|
|
121
|
+
const targets = this.getResolvedTargets();
|
|
122
|
+
if (targets.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const target of targets) {
|
|
127
|
+
try {
|
|
128
|
+
if (!fs.existsSync(target)) {
|
|
129
|
+
return 'copy';
|
|
130
|
+
}
|
|
131
|
+
const stats = fs.lstatSync(target);
|
|
132
|
+
if (!stats.isSymbolicLink()) {
|
|
133
|
+
return 'copy';
|
|
134
|
+
}
|
|
135
|
+
const realTarget = fs.realpathSync(target);
|
|
136
|
+
if (realTarget !== this.skillsDir) {
|
|
137
|
+
return 'copy';
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return 'copy';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return 'link';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 开始监听目录
|
|
149
|
+
*/
|
|
150
|
+
async startWatching() {
|
|
151
|
+
if (this.watcher) return;
|
|
152
|
+
const targets = this.getResolvedTargets();
|
|
153
|
+
if (targets.length === 0) return;
|
|
154
|
+
|
|
155
|
+
logger.info('开始监听技能目录变更并自动同步:', this.skillsDir);
|
|
156
|
+
|
|
157
|
+
this.watcher = createSyncer(this.skillsDir, targets, {
|
|
158
|
+
preferLink: true,
|
|
159
|
+
fallbackToCopy: true,
|
|
160
|
+
deleteOrphaned: false,
|
|
161
|
+
ignorePatterns: ['**/.*']
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const method = await this.watcher.sync(); // 启动前先同步一次
|
|
165
|
+
this.syncConfig.lastSyncMethod = method || null;
|
|
166
|
+
await this.saveConfig();
|
|
167
|
+
if (method !== 'link') {
|
|
168
|
+
this.watcher.watch();
|
|
169
|
+
} else {
|
|
170
|
+
logger.info('符号链接模式无需监听自动同步');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 停止监听
|
|
176
|
+
*/
|
|
177
|
+
async stopWatching() {
|
|
178
|
+
if (this.watcher) {
|
|
179
|
+
await this.watcher.stopWatch();
|
|
180
|
+
this.watcher = null;
|
|
181
|
+
logger.info('已停止监听技能目录');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 执行同步
|
|
187
|
+
*/
|
|
188
|
+
async runSync() {
|
|
189
|
+
const targets = this.getResolvedTargets();
|
|
190
|
+
if (targets.length === 0) {
|
|
191
|
+
logger.warn('未配置同步目标目录,跳过同步');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
logger.info('开始执行技能全局同步...');
|
|
197
|
+
|
|
198
|
+
const syncer = createSyncer(this.skillsDir, targets, {
|
|
199
|
+
preferLink: true,
|
|
200
|
+
fallbackToCopy: true,
|
|
201
|
+
deleteOrphaned: false,
|
|
202
|
+
ignorePatterns: ['**/.*']
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const method = await syncer.sync();
|
|
206
|
+
|
|
207
|
+
this.syncConfig.lastSyncTime = new Date().toISOString();
|
|
208
|
+
this.syncConfig.lastSyncMethod = method || null;
|
|
209
|
+
this.syncConfig.error = null;
|
|
210
|
+
await this.saveConfig();
|
|
211
|
+
|
|
212
|
+
logger.info('技能全局同步完成');
|
|
213
|
+
return { method };
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.error('执行技能同步时发生错误:', error);
|
|
216
|
+
this.syncConfig.error = error.message;
|
|
217
|
+
await this.saveConfig();
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const skillSyncService = new SkillSyncService();
|