@shareai-lab/kode-sdk 2.7.1 → 2.7.2
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/dist/core/agent/breakpoint-manager.js +36 -0
- package/dist/core/agent/message-queue.js +57 -0
- package/dist/core/agent/permission-manager.js +32 -0
- package/dist/core/agent/todo-manager.js +91 -0
- package/dist/core/agent/tool-runner.js +45 -0
- package/dist/core/agent.js +2035 -0
- package/dist/core/config.js +2 -0
- package/dist/core/context-manager.js +241 -0
- package/dist/core/errors.js +49 -0
- package/dist/core/events.js +329 -0
- package/dist/core/file-pool.d.ts +2 -0
- package/dist/core/file-pool.js +125 -0
- package/dist/core/hooks.js +71 -0
- package/dist/core/permission-modes.js +61 -0
- package/dist/core/pool.js +301 -0
- package/dist/core/room.js +57 -0
- package/dist/core/scheduler.js +58 -0
- package/dist/core/skills/index.js +20 -0
- package/dist/core/skills/management-manager.js +557 -0
- package/dist/core/skills/manager.js +243 -0
- package/dist/core/skills/operation-queue.js +113 -0
- package/dist/core/skills/sandbox-file-manager.js +183 -0
- package/dist/core/skills/types.js +9 -0
- package/dist/core/skills/xml-generator.js +70 -0
- package/dist/core/template.js +35 -0
- package/dist/core/time-bridge.js +100 -0
- package/dist/core/todo.js +89 -0
- package/dist/core/types.js +3 -0
- package/dist/index.js +148 -60461
- package/dist/infra/db/postgres/postgres-store.js +1073 -0
- package/dist/infra/db/sqlite/sqlite-store.js +800 -0
- package/dist/infra/e2b/e2b-fs.js +128 -0
- package/dist/infra/e2b/e2b-sandbox.js +156 -0
- package/dist/infra/e2b/e2b-template.js +105 -0
- package/dist/infra/e2b/index.js +9 -0
- package/dist/infra/e2b/types.js +2 -0
- package/dist/infra/provider.js +67 -0
- package/dist/infra/providers/anthropic.js +308 -0
- package/dist/infra/providers/core/errors.js +353 -0
- package/dist/infra/providers/core/fork.js +418 -0
- package/dist/infra/providers/core/index.js +76 -0
- package/dist/infra/providers/core/logger.js +191 -0
- package/dist/infra/providers/core/retry.js +189 -0
- package/dist/infra/providers/core/usage.js +376 -0
- package/dist/infra/providers/gemini.js +493 -0
- package/dist/infra/providers/index.js +83 -0
- package/dist/infra/providers/openai.js +662 -0
- package/dist/infra/providers/types.js +20 -0
- package/dist/infra/providers/utils.js +400 -0
- package/dist/infra/sandbox-factory.js +30 -0
- package/dist/infra/sandbox.js +243 -0
- package/dist/infra/store/factory.js +80 -0
- package/dist/infra/store/index.js +26 -0
- package/dist/infra/store/json-store.js +606 -0
- package/dist/infra/store/types.js +2 -0
- package/dist/infra/store.js +29 -0
- package/dist/tools/bash_kill/index.js +35 -0
- package/dist/tools/bash_kill/prompt.js +14 -0
- package/dist/tools/bash_logs/index.js +40 -0
- package/dist/tools/bash_logs/prompt.js +14 -0
- package/dist/tools/bash_run/index.js +61 -0
- package/dist/tools/bash_run/prompt.js +18 -0
- package/dist/tools/builtin.js +26 -0
- package/dist/tools/define.js +214 -0
- package/dist/tools/fs_edit/index.js +62 -0
- package/dist/tools/fs_edit/prompt.js +15 -0
- package/dist/tools/fs_glob/index.js +40 -0
- package/dist/tools/fs_glob/prompt.js +15 -0
- package/dist/tools/fs_grep/index.js +66 -0
- package/dist/tools/fs_grep/prompt.js +16 -0
- package/dist/tools/fs_multi_edit/index.js +106 -0
- package/dist/tools/fs_multi_edit/prompt.js +16 -0
- package/dist/tools/fs_read/index.js +40 -0
- package/dist/tools/fs_read/prompt.js +16 -0
- package/dist/tools/fs_write/index.js +40 -0
- package/dist/tools/fs_write/prompt.js +15 -0
- package/dist/tools/index.js +61 -0
- package/dist/tools/mcp.js +185 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/scripts.js +205 -0
- package/dist/tools/skills.js +115 -0
- package/dist/tools/task_run/index.js +58 -0
- package/dist/tools/task_run/prompt.js +25 -0
- package/dist/tools/todo_read/index.js +29 -0
- package/dist/tools/todo_read/prompt.js +18 -0
- package/dist/tools/todo_write/index.js +42 -0
- package/dist/tools/todo_write/prompt.js +23 -0
- package/dist/tools/tool.js +211 -0
- package/dist/tools/toolkit.js +98 -0
- package/dist/tools/type-inference.js +207 -0
- package/dist/utils/agent-id.js +28 -0
- package/dist/utils/logger.js +44 -0
- package/dist/utils/session-id.js +64 -0
- package/package.json +7 -38
- package/dist/index.js.map +0 -7
- package/dist/index.mjs +0 -60385
- package/dist/index.mjs.map +0 -7
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 技能管理器模块(路径1 - 技能管理)
|
|
4
|
+
*
|
|
5
|
+
* 设计原则 (UNIX哲学):
|
|
6
|
+
* - 简洁: 只负责技能文件系统的CRUD操作
|
|
7
|
+
* - 模块化: 协调OperationQueue、SandboxFileManager进行文件系统操作
|
|
8
|
+
* - 隔离: 与Agent运行时完全隔离,不参与Agent使用
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 重要说明:
|
|
11
|
+
* - 此模块专门用于路径1(技能管理)
|
|
12
|
+
* - 与路径2(Agent运行时)完全独立
|
|
13
|
+
* - 请勿与SkillsManager混淆
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.SkillsManagementManager = void 0;
|
|
50
|
+
const fs = __importStar(require("fs/promises"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const crypto = __importStar(require("crypto"));
|
|
53
|
+
const manager_1 = require("./manager");
|
|
54
|
+
const operation_queue_1 = require("./operation-queue");
|
|
55
|
+
const sandbox_file_manager_1 = require("./sandbox-file-manager");
|
|
56
|
+
const sandbox_factory_1 = require("../../infra/sandbox-factory");
|
|
57
|
+
const logger_1 = require("../../utils/logger");
|
|
58
|
+
/**
|
|
59
|
+
* 技能管理器类
|
|
60
|
+
*
|
|
61
|
+
* 职责:
|
|
62
|
+
* - 提供所有技能管理操作的统一接口(CRUD操作)
|
|
63
|
+
* - 协调OperationQueue、SandboxFileManager进行文件系统操作
|
|
64
|
+
* - 处理业务逻辑和权限验证
|
|
65
|
+
* - ❌ 不参与Agent运行时
|
|
66
|
+
* - ❌ 不提供技能加载、扫描等Agent使用的功能
|
|
67
|
+
*/
|
|
68
|
+
class SkillsManagementManager {
|
|
69
|
+
constructor(skillsDir, sandboxFactory, archivedDir // 可选,默认为 skills/.archived/
|
|
70
|
+
) {
|
|
71
|
+
this.skillsDir = path.resolve(skillsDir);
|
|
72
|
+
this.archivedDir = archivedDir ? path.resolve(archivedDir) : path.join(this.skillsDir, '.archived');
|
|
73
|
+
this.skillsManager = new manager_1.SkillsManager(this.skillsDir);
|
|
74
|
+
this.operationQueue = new operation_queue_1.OperationQueue();
|
|
75
|
+
this.sandboxFileManager = new sandbox_file_manager_1.SandboxFileManager(sandboxFactory || new sandbox_factory_1.SandboxFactory());
|
|
76
|
+
logger_1.logger.log(`[SkillsManagementManager] Initialized with skills directory: ${this.skillsDir}`);
|
|
77
|
+
logger_1.logger.log(`[SkillsManagementManager] Archived directory: ${this.archivedDir}`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 获取所有在线技能列表(不包含archived技能)
|
|
81
|
+
*/
|
|
82
|
+
async listSkills() {
|
|
83
|
+
// 扫描所有技能
|
|
84
|
+
const allSkills = await this.skillsManager.getSkillsMetadata();
|
|
85
|
+
// 过滤掉archived技能(排除.archived目录)
|
|
86
|
+
const onlineSkills = allSkills.filter((skill) => {
|
|
87
|
+
return !skill.baseDir.includes('/.archived/') &&
|
|
88
|
+
!skill.baseDir.includes('\\.archived\\');
|
|
89
|
+
});
|
|
90
|
+
// 添加文件统计信息
|
|
91
|
+
const skillsWithInfo = [];
|
|
92
|
+
for (const skill of onlineSkills) {
|
|
93
|
+
const stat = await this.safeGetFileStat(skill.path);
|
|
94
|
+
skillsWithInfo.push({
|
|
95
|
+
...skill,
|
|
96
|
+
createdAt: stat?.birthtime?.toISOString(),
|
|
97
|
+
updatedAt: stat?.mtime?.toISOString(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return skillsWithInfo;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 获取单个在线技能详细信息
|
|
104
|
+
* @param skillName 技能名称
|
|
105
|
+
*/
|
|
106
|
+
async getSkillInfo(skillName) {
|
|
107
|
+
// 检查技能是否在线(不在archived中)
|
|
108
|
+
const skill = await this.skillsManager.loadSkillContent(skillName);
|
|
109
|
+
if (!skill) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// 验证技能不是archived(排除.archived目录)
|
|
113
|
+
if (skill.metadata.baseDir.includes('/.archived/') ||
|
|
114
|
+
skill.metadata.baseDir.includes('\\.archived\\')) {
|
|
115
|
+
throw new Error(`Cannot get info for archived skill: ${skillName}`);
|
|
116
|
+
}
|
|
117
|
+
// 获取文件树
|
|
118
|
+
const files = await this.getSkillFileTree(skillName);
|
|
119
|
+
// 获取文件统计信息
|
|
120
|
+
const stat = await this.safeGetFileStat(skill.metadata.path);
|
|
121
|
+
return {
|
|
122
|
+
name: skill.metadata.name,
|
|
123
|
+
description: skill.metadata.description,
|
|
124
|
+
path: skill.metadata.path,
|
|
125
|
+
baseDir: skill.metadata.baseDir,
|
|
126
|
+
createdAt: stat?.birthtime?.toISOString(),
|
|
127
|
+
updatedAt: stat?.mtime?.toISOString(),
|
|
128
|
+
files,
|
|
129
|
+
references: skill.references,
|
|
130
|
+
scripts: skill.scripts,
|
|
131
|
+
assets: skill.assets,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 获取已归档技能列表(只读,不支持修改)
|
|
136
|
+
*/
|
|
137
|
+
async listArchivedSkills() {
|
|
138
|
+
// 使用配置的归档目录
|
|
139
|
+
const archivedDir = this.archivedDir;
|
|
140
|
+
// 检查archived目录是否存在
|
|
141
|
+
const exists = await this.fileExists(archivedDir);
|
|
142
|
+
if (!exists) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
// 读取archived目录
|
|
147
|
+
const entries = await fs.readdir(archivedDir, { withFileTypes: true });
|
|
148
|
+
const archivedSkills = [];
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
if (!entry.isDirectory()) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const archivedPath = path.join(archivedDir, entry.name);
|
|
154
|
+
// 检查是否包含SKILL.md
|
|
155
|
+
const skillMdPath = path.join(archivedPath, 'SKILL.md');
|
|
156
|
+
if (!(await this.fileExists(skillMdPath))) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// 提取原始名称和归档时间(支持带毫秒和不带毫秒两种格式)
|
|
160
|
+
const match = entry.name.match(/^(.+?)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(?:-\d{3})?Z)$/);
|
|
161
|
+
if (!match) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const originalName = match[1];
|
|
165
|
+
// 解析时间戳(支持带毫秒和不带毫秒两种格式)
|
|
166
|
+
// 格式1: 2026-01-15T05-05-01-350Z (带毫秒)
|
|
167
|
+
// 格式2: 2026-01-15T05-05-01Z (不带毫秒)
|
|
168
|
+
const timestampMatch = match[2].match(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})(?:-(\d{3}))?Z$/);
|
|
169
|
+
if (!timestampMatch) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const [, date, hour, min, sec, ms] = timestampMatch;
|
|
173
|
+
const isoTimestamp = ms
|
|
174
|
+
? `${date}T${hour}:${min}:${sec}.${ms}Z`
|
|
175
|
+
: `${date}T${hour}:${min}:${sec}Z`;
|
|
176
|
+
const archivedAt = new Date(isoTimestamp).toISOString();
|
|
177
|
+
// 获取文件统计信息
|
|
178
|
+
const stat = await this.safeGetFileStat(archivedPath);
|
|
179
|
+
archivedSkills.push({
|
|
180
|
+
originalName,
|
|
181
|
+
archivedName: entry.name,
|
|
182
|
+
archivedPath,
|
|
183
|
+
archivedAt: stat?.mtime?.toISOString() || archivedAt,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return archivedSkills.sort((a, b) => b.archivedAt.localeCompare(a.archivedAt));
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
logger_1.logger.error('[SkillsManagementManager] Error listing archived skills:', error);
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 创建新技能
|
|
195
|
+
* @param skillName 技能名称
|
|
196
|
+
* @param options 技能配置(名称、描述等)
|
|
197
|
+
*/
|
|
198
|
+
async createSkill(skillName, options) {
|
|
199
|
+
// 包装为操作任务
|
|
200
|
+
const task = {
|
|
201
|
+
id: crypto.randomUUID(),
|
|
202
|
+
type: operation_queue_1.OperationType.CREATE,
|
|
203
|
+
targetSkill: skillName,
|
|
204
|
+
status: 'pending',
|
|
205
|
+
execute: async () => {
|
|
206
|
+
await this.doCreateSkill(skillName, options);
|
|
207
|
+
},
|
|
208
|
+
createdAt: new Date(),
|
|
209
|
+
};
|
|
210
|
+
// 入队并等待完成
|
|
211
|
+
await this.operationQueue.enqueue(task);
|
|
212
|
+
await this.waitForTask(task);
|
|
213
|
+
// 检查是否有错误
|
|
214
|
+
if (task.error) {
|
|
215
|
+
throw task.error;
|
|
216
|
+
}
|
|
217
|
+
// 返回创建的技能详细信息
|
|
218
|
+
const skillDetail = await this.getSkillInfo(skillName);
|
|
219
|
+
if (!skillDetail) {
|
|
220
|
+
throw new Error(`Failed to get skill info after creation: ${skillName}`);
|
|
221
|
+
}
|
|
222
|
+
return skillDetail;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 重命名技能
|
|
226
|
+
* @param oldName 旧技能名称
|
|
227
|
+
* @param newName 新技能名称
|
|
228
|
+
*/
|
|
229
|
+
async renameSkill(oldName, newName) {
|
|
230
|
+
// 包装为操作任务
|
|
231
|
+
const task = {
|
|
232
|
+
id: crypto.randomUUID(),
|
|
233
|
+
type: operation_queue_1.OperationType.RENAME,
|
|
234
|
+
targetSkill: `${oldName} -> ${newName}`,
|
|
235
|
+
status: 'pending',
|
|
236
|
+
execute: async () => {
|
|
237
|
+
await this.doRenameSkill(oldName, newName);
|
|
238
|
+
},
|
|
239
|
+
createdAt: new Date(),
|
|
240
|
+
};
|
|
241
|
+
// 入队并等待完成
|
|
242
|
+
await this.operationQueue.enqueue(task);
|
|
243
|
+
await this.waitForTask(task);
|
|
244
|
+
// 检查是否有错误
|
|
245
|
+
if (task.error) {
|
|
246
|
+
throw task.error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 编辑技能文件
|
|
251
|
+
* @param skillName 技能名称
|
|
252
|
+
* @param filePath 文件路径(相对于技能根目录,如"SKILL.md")
|
|
253
|
+
* @param content 文件内容
|
|
254
|
+
* @param useSandbox 是否使用sandbox(默认true)
|
|
255
|
+
*/
|
|
256
|
+
async editSkillFile(skillName, filePath, content, useSandbox = true) {
|
|
257
|
+
// 包装为操作任务
|
|
258
|
+
const task = {
|
|
259
|
+
id: crypto.randomUUID(),
|
|
260
|
+
type: operation_queue_1.OperationType.EDIT,
|
|
261
|
+
targetSkill: skillName,
|
|
262
|
+
status: 'pending',
|
|
263
|
+
execute: async () => {
|
|
264
|
+
await this.doEditSkillFile(skillName, filePath, content, useSandbox);
|
|
265
|
+
},
|
|
266
|
+
createdAt: new Date(),
|
|
267
|
+
};
|
|
268
|
+
// 入队并等待完成
|
|
269
|
+
await this.operationQueue.enqueue(task);
|
|
270
|
+
await this.waitForTask(task);
|
|
271
|
+
// 检查是否有错误
|
|
272
|
+
if (task.error) {
|
|
273
|
+
throw task.error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 删除技能(移动到archived)
|
|
278
|
+
* @param skillName 技能名称
|
|
279
|
+
*/
|
|
280
|
+
async deleteSkill(skillName) {
|
|
281
|
+
// 包装为操作任务
|
|
282
|
+
const task = {
|
|
283
|
+
id: crypto.randomUUID(),
|
|
284
|
+
type: operation_queue_1.OperationType.DELETE,
|
|
285
|
+
targetSkill: skillName,
|
|
286
|
+
status: 'pending',
|
|
287
|
+
execute: async () => {
|
|
288
|
+
await this.doDeleteSkill(skillName);
|
|
289
|
+
},
|
|
290
|
+
createdAt: new Date(),
|
|
291
|
+
};
|
|
292
|
+
// 入队并等待完成
|
|
293
|
+
await this.operationQueue.enqueue(task);
|
|
294
|
+
await this.waitForTask(task);
|
|
295
|
+
// 检查是否有错误
|
|
296
|
+
if (task.error) {
|
|
297
|
+
throw task.error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 恢复已删除的技能
|
|
302
|
+
* @param archivedSkillName archived中的技能名称(含时间戳)
|
|
303
|
+
*/
|
|
304
|
+
async restoreSkill(archivedSkillName) {
|
|
305
|
+
// 包装为操作任务
|
|
306
|
+
const task = {
|
|
307
|
+
id: crypto.randomUUID(),
|
|
308
|
+
type: operation_queue_1.OperationType.RESTORE,
|
|
309
|
+
targetSkill: archivedSkillName,
|
|
310
|
+
status: 'pending',
|
|
311
|
+
execute: async () => {
|
|
312
|
+
await this.doRestoreSkill(archivedSkillName);
|
|
313
|
+
},
|
|
314
|
+
createdAt: new Date(),
|
|
315
|
+
};
|
|
316
|
+
// 入队并等待完成
|
|
317
|
+
await this.operationQueue.enqueue(task);
|
|
318
|
+
await this.waitForTask(task);
|
|
319
|
+
// 检查是否有错误
|
|
320
|
+
if (task.error) {
|
|
321
|
+
throw task.error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 获取技能文件树(仅在线技能)
|
|
326
|
+
* @param skillName 技能名称
|
|
327
|
+
*/
|
|
328
|
+
async getSkillFileTree(skillName) {
|
|
329
|
+
// 获取技能信息
|
|
330
|
+
const skill = await this.skillsManager.loadSkillContent(skillName);
|
|
331
|
+
if (!skill) {
|
|
332
|
+
throw new Error(`Skill not found: ${skillName}`);
|
|
333
|
+
}
|
|
334
|
+
// 验证技能不是archived(排除.archived目录)
|
|
335
|
+
if (skill.metadata.baseDir.includes('/.archived/') ||
|
|
336
|
+
skill.metadata.baseDir.includes('\\.archived\\')) {
|
|
337
|
+
throw new Error(`Cannot get file tree for archived skill: ${skillName}`);
|
|
338
|
+
}
|
|
339
|
+
// 使用SandboxFileManager获取文件树
|
|
340
|
+
return await this.sandboxFileManager.listFiles(skill.metadata.baseDir, '.');
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* 获取队列状态
|
|
344
|
+
*/
|
|
345
|
+
getQueueStatus() {
|
|
346
|
+
return this.operationQueue.getQueueStatus();
|
|
347
|
+
}
|
|
348
|
+
// ==================== 私有方法 ====================
|
|
349
|
+
/**
|
|
350
|
+
* 执行创建技能
|
|
351
|
+
*/
|
|
352
|
+
async doCreateSkill(skillName, options) {
|
|
353
|
+
// 1. 验证技能名称
|
|
354
|
+
if (!this.isValidSkillName(skillName)) {
|
|
355
|
+
throw new Error(`Invalid skill name: ${skillName}`);
|
|
356
|
+
}
|
|
357
|
+
// 优先检查archived技能是否已存在(因为 SkillsManager 也会扫描 .archived 目录)
|
|
358
|
+
const archivedSkills = await this.listArchivedSkills();
|
|
359
|
+
const archivedSkill = archivedSkills.find(s => s.originalName === skillName);
|
|
360
|
+
if (archivedSkill) {
|
|
361
|
+
throw new Error(`Archived skill with name '${skillName}' already exists. Please restore or permanently delete it first.`);
|
|
362
|
+
}
|
|
363
|
+
// 检查在线技能是否已存在(排除 .archived 目录中的技能)
|
|
364
|
+
const existingSkill = await this.skillsManager.loadSkillContent(skillName);
|
|
365
|
+
if (existingSkill) {
|
|
366
|
+
// 二次验证:确保技能不在 .archived 目录中
|
|
367
|
+
if (existingSkill.metadata.baseDir.includes('/.archived/') ||
|
|
368
|
+
existingSkill.metadata.baseDir.includes('\\.archived\\')) {
|
|
369
|
+
throw new Error(`Archived skill with name '${skillName}' already exists. Please restore or permanently delete it first.`);
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`Skill already exists: ${skillName}`);
|
|
372
|
+
}
|
|
373
|
+
// 2. 创建目录结构
|
|
374
|
+
const skillDir = path.join(this.skillsDir, skillName);
|
|
375
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
376
|
+
await fs.mkdir(path.join(skillDir, 'references'));
|
|
377
|
+
await fs.mkdir(path.join(skillDir, 'scripts'));
|
|
378
|
+
await fs.mkdir(path.join(skillDir, 'assets'));
|
|
379
|
+
// 3. 生成SKILL.md
|
|
380
|
+
const skillMdContent = this.generateSkillMd(options);
|
|
381
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillMdContent, 'utf-8');
|
|
382
|
+
logger_1.logger.log(`[SkillsManagementManager] Skill created: ${skillName}`);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 执行重命名技能
|
|
386
|
+
*/
|
|
387
|
+
async doRenameSkill(oldName, newName) {
|
|
388
|
+
// 1. 验证旧技能存在
|
|
389
|
+
const oldSkill = await this.skillsManager.loadSkillContent(oldName);
|
|
390
|
+
if (!oldSkill) {
|
|
391
|
+
throw new Error(`Skill not found: ${oldName}`);
|
|
392
|
+
}
|
|
393
|
+
// 2. 验证新名称
|
|
394
|
+
if (!this.isValidSkillName(newName)) {
|
|
395
|
+
throw new Error(`Invalid skill name: ${newName}`);
|
|
396
|
+
}
|
|
397
|
+
const newSkill = await this.skillsManager.loadSkillContent(newName);
|
|
398
|
+
if (newSkill) {
|
|
399
|
+
throw new Error(`Skill already exists: ${newName}`);
|
|
400
|
+
}
|
|
401
|
+
// 3. 重命名目录
|
|
402
|
+
const oldPath = path.join(this.skillsDir, oldName);
|
|
403
|
+
const newPath = path.join(this.skillsDir, newName);
|
|
404
|
+
await fs.rename(oldPath, newPath);
|
|
405
|
+
// 4. 更新SKILL.md中的name字段
|
|
406
|
+
const skillMdPath = path.join(newPath, 'SKILL.md');
|
|
407
|
+
let content = await fs.readFile(skillMdPath, 'utf-8');
|
|
408
|
+
content = content.replace(/^name:\s*.+$/m, `name: ${newName}`);
|
|
409
|
+
await fs.writeFile(skillMdPath, content, 'utf-8');
|
|
410
|
+
logger_1.logger.log(`[SkillsManagementManager] Skill renamed: ${oldName} -> ${newName}`);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 执行编辑技能文件
|
|
414
|
+
*/
|
|
415
|
+
async doEditSkillFile(skillName, filePath, content, useSandbox) {
|
|
416
|
+
// 1. 获取技能信息
|
|
417
|
+
const skill = await this.skillsManager.loadSkillContent(skillName);
|
|
418
|
+
if (!skill) {
|
|
419
|
+
throw new Error(`Skill not found: ${skillName}`);
|
|
420
|
+
}
|
|
421
|
+
// 1.1 验证技能是否为archived(不支持编辑.archived中的技能)
|
|
422
|
+
if (skill.metadata.baseDir.includes('/.archived/') ||
|
|
423
|
+
skill.metadata.baseDir.includes('\\.archived\\')) {
|
|
424
|
+
throw new Error(`Cannot edit archived skill: ${skillName}. Please restore it first.`);
|
|
425
|
+
}
|
|
426
|
+
// 2. 验证文件路径(防止路径穿越)
|
|
427
|
+
const normalizedPath = path.normalize(filePath);
|
|
428
|
+
if (normalizedPath.startsWith('..') || path.isAbsolute(normalizedPath)) {
|
|
429
|
+
throw new Error(`Invalid file path: ${filePath}`);
|
|
430
|
+
}
|
|
431
|
+
// 3. 写入文件
|
|
432
|
+
if (useSandbox) {
|
|
433
|
+
// 使用sandbox写入(安全)
|
|
434
|
+
await this.sandboxFileManager.writeFile(skill.metadata.baseDir, normalizedPath, content);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// 直接写入(不推荐)
|
|
438
|
+
const fullPath = path.join(skill.metadata.baseDir, normalizedPath);
|
|
439
|
+
await fs.writeFile(fullPath, content, 'utf-8');
|
|
440
|
+
}
|
|
441
|
+
logger_1.logger.log(`[SkillsManagementManager] File edited: ${skillName}/${filePath}`);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* 执行删除技能
|
|
445
|
+
*/
|
|
446
|
+
async doDeleteSkill(skillName) {
|
|
447
|
+
// 1. 验证技能存在
|
|
448
|
+
const skill = await this.skillsManager.loadSkillContent(skillName);
|
|
449
|
+
if (!skill) {
|
|
450
|
+
throw new Error(`Skill not found: ${skillName}`);
|
|
451
|
+
}
|
|
452
|
+
// 2. 确保archived目录存在(使用配置的归档目录)
|
|
453
|
+
const archivedDir = this.archivedDir;
|
|
454
|
+
await fs.mkdir(archivedDir, { recursive: true });
|
|
455
|
+
// 3. 生成归档名称
|
|
456
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
457
|
+
const archivedName = `${skillName}_${timestamp}`;
|
|
458
|
+
const archivedPath = path.join(archivedDir, archivedName);
|
|
459
|
+
// 4. 移动到archived
|
|
460
|
+
await fs.rename(skill.metadata.baseDir, archivedPath);
|
|
461
|
+
logger_1.logger.log(`[SkillsManagementManager] Skill archived: ${skillName} -> ${archivedName}`);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 执行恢复技能
|
|
465
|
+
*/
|
|
466
|
+
async doRestoreSkill(archivedSkillName) {
|
|
467
|
+
// 1. 查找archived技能(使用配置的归档目录)
|
|
468
|
+
const archivedDir = this.archivedDir;
|
|
469
|
+
const archivedPath = path.join(archivedDir, archivedSkillName);
|
|
470
|
+
const exists = await this.fileExists(archivedPath);
|
|
471
|
+
if (!exists) {
|
|
472
|
+
throw new Error(`Archived skill not found: ${archivedSkillName}`);
|
|
473
|
+
}
|
|
474
|
+
// 2. 提取原始名称(去掉时间戳后缀,支持带毫秒和不带毫秒两种格式)
|
|
475
|
+
const originalName = archivedSkillName.replace(/_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(?:-\d{3})?Z$/, '');
|
|
476
|
+
// 3. 检查目标位置是否已存在
|
|
477
|
+
const targetPath = path.join(this.skillsDir, originalName);
|
|
478
|
+
if (await this.fileExists(targetPath)) {
|
|
479
|
+
throw new Error(`Skill already exists: ${originalName}`);
|
|
480
|
+
}
|
|
481
|
+
// 4. 移回skills目录
|
|
482
|
+
await fs.rename(archivedPath, targetPath);
|
|
483
|
+
logger_1.logger.log(`[SkillsManagementManager] Skill restored: ${archivedSkillName} -> ${originalName}`);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* 验证技能名称
|
|
487
|
+
*/
|
|
488
|
+
isValidSkillName(name) {
|
|
489
|
+
// 只允许字母、数字、连字符、下划线
|
|
490
|
+
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 50;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* 生成SKILL.md内容
|
|
494
|
+
*/
|
|
495
|
+
generateSkillMd(options) {
|
|
496
|
+
const { name, description = '' } = options;
|
|
497
|
+
return `---
|
|
498
|
+
name: ${name}
|
|
499
|
+
description: ${description}
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
# ${name}
|
|
503
|
+
|
|
504
|
+
This is a custom skill created for ${name}.
|
|
505
|
+
|
|
506
|
+
## Usage
|
|
507
|
+
|
|
508
|
+
Describe how to use this skill here.
|
|
509
|
+
|
|
510
|
+
## Configuration
|
|
511
|
+
|
|
512
|
+
Add any configuration details here.
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* 检查文件是否存在
|
|
517
|
+
*/
|
|
518
|
+
async fileExists(filePath) {
|
|
519
|
+
try {
|
|
520
|
+
await fs.access(filePath);
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* 安全获取文件统计信息
|
|
529
|
+
*/
|
|
530
|
+
async safeGetFileStat(filePath) {
|
|
531
|
+
try {
|
|
532
|
+
return await fs.stat(filePath);
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* 等待任务完成
|
|
540
|
+
*/
|
|
541
|
+
async waitForTask(task) {
|
|
542
|
+
// 轮询任务状态,最多等待30秒
|
|
543
|
+
const maxWaitTime = 30000;
|
|
544
|
+
const pollInterval = 100;
|
|
545
|
+
let totalWaited = 0;
|
|
546
|
+
while (task.status !== 'completed' &&
|
|
547
|
+
task.status !== 'failed' &&
|
|
548
|
+
totalWaited < maxWaitTime) {
|
|
549
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
550
|
+
totalWaited += pollInterval;
|
|
551
|
+
}
|
|
552
|
+
if (task.status !== 'completed' && task.status !== 'failed') {
|
|
553
|
+
throw new Error(`Operation timeout: ${task.type} - ${task.targetSkill}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
exports.SkillsManagementManager = SkillsManagementManager;
|