重构agent结构前提交

This commit is contained in:
ACT丶流星雨 2026-03-20 13:10:59 +08:00
parent 68f771b7bc
commit 81eb0395a0

View File

@ -23,10 +23,16 @@ function parseFrontmatter(content: string): { name: string; description: string
for (let i = 0; i < lines.length; ) {
const colonIndex = lines[i].indexOf(":");
if (colonIndex === -1) { i++; continue; }
if (colonIndex === -1) {
i++;
continue;
}
const key = lines[i].slice(0, colonIndex).trim();
if (!key) { i++; continue; }
if (!key) {
i++;
continue;
}
let value = lines[i].slice(colonIndex + 1).trim();
i++;
@ -44,8 +50,7 @@ function parseFrontmatter(content: string): { name: string; description: string
result[key] = value;
}
if (!result.name) throw new Error("Frontmatter missing required field: name");
if (!result.description) throw new Error("Frontmatter missing required field: description");
if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description");
return { name: result.name, description: result.description };
}
@ -57,13 +62,17 @@ function stripFrontmatter(content: string): string {
async function listResources(dir: string, base = ""): Promise<string[]> {
let entries;
try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return []; }
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
return [];
}
const files: string[] = [];
for (const entry of entries) {
const rel = base ? `${base}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
files.push(...await listResources(path.join(dir, entry.name), rel));
files.push(...(await listResources(path.join(dir, entry.name), rel)));
} else if (entry.name !== "SKILL.md") {
files.push(rel);
}
@ -71,22 +80,39 @@ async function listResources(dir: string, base = ""): Promise<string[]> {
return files;
}
// ==================== 读取单个技能 ====================
async function readSkillFromDir(skillDir: string): Promise<SkillRecord | null> {
const location = path.join(skillDir, "SKILL.md");
let content: string;
try {
content = await fs.readFile(location, "utf-8");
} catch {
return null;
}
try {
const meta = parseFrontmatter(content);
console.log(`[Skill] ✅ 发现技能:${meta.name}${meta.description}`);
return { ...meta, location, baseDir: skillDir };
} catch (e) {
console.log(`[Skill] ⚠️ 解析失败 "${skillDir}"${(e as Error).message}`);
return null;
}
}
// ==================== 构建技能目录 ====================
function buildCatalog(skill: SkillRecord): string {
return [
"## Skills",
"以下技能提供了专业任务的专用指令。",
"当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。",
"加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。",
"",
"<available_skills>",
` <skill>`,
` <name>${skill.name}</name>`,
` <description>${skill.description}</description>`,
` </skill>`,
"</available_skills>",
].join("\n");
function buildCatalog(skills: SkillRecord[]): string {
const entries = skills.map((s) => ` <skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n </skill>`).join("\n");
return `## Skills
activate_skill
read_skill_file
<available_skills>
${entries}
</available_skills>`;
}
// ==================== 激活 + 执行工具 ====================
@ -103,19 +129,13 @@ function createSkillTools(skills: SkillRecord[]) {
}),
execute: async ({ name }) => {
const skill = skills.find((s) => s.name === name);
if (!skill) {
console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`);
return { error: `Skill '${name}' not found` };
}
if (activated.has(name)) {
console.log(`[Skill] 技能 "${name}" 已在当前会话中激活,跳过重复注入`);
return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
}
if (!skill) return { error: `Skill '${name}' not found` };
if (activated.has(name)) return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
let content: string;
try { content = await fs.readFile(skill.location, "utf-8"); } catch {
console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`);
try {
content = await fs.readFile(skill.location, "utf-8");
} catch {
return { error: `Failed to read SKILL.md for '${name}'` };
}
@ -123,22 +143,17 @@ function createSkillTools(skills: SkillRecord[]) {
const resources = await listResources(skill.baseDir);
activated.add(name);
console.log(`[Skill] 📖 已激活技能:${skill.name}${body.length} 字符,${resources.length} 个资源文件)`);
const resourcesXml = resources.length > 0
? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>`
: "";
const resourcesXml =
resources.length > 0 ? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>` : "";
return {
content: [
`<skill_content name="${skill.name}">`,
body,
"",
`Skill directory: ${skill.baseDir}`,
`相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。`,
resourcesXml,
`</skill_content>`,
].join("\n"),
content: `<skill_content name="${skill.name}">
${body}
Skill directory: ${skill.baseDir}
使 read_skill_file
${resourcesXml}
</skill_content>`,
};
},
}),
@ -151,23 +166,14 @@ function createSkillTools(skills: SkillRecord[]) {
}),
execute: async ({ skillName, filePath: relPath }) => {
const skill = skills.find((s) => s.name === skillName);
if (!skill) {
console.log(`[Skill] ❌ 读取失败:未找到技能 "${skillName}"`);
return { error: `Skill '${skillName}' not found` };
}
if (!skill) return { error: `Skill '${skillName}' not found` };
const fullPath = path.resolve(path.join(skill.baseDir, relPath));
if (!isPathInside(fullPath, skill.baseDir)) {
console.log(`[Skill] 🚫 路径越界已拦截:"${relPath}" 超出技能目录范围`);
return { error: "Access denied: path is outside skill directory" };
}
if (!isPathInside(fullPath, skill.baseDir)) return { error: "Access denied: path is outside skill directory" };
try {
const fileContent = await fs.readFile(fullPath, "utf-8");
console.log(`[Skill] 📄 已读取文件:${skillName}/${relPath}${fileContent.length} 字符)`);
return { content: fileContent };
return { content: await fs.readFile(fullPath, "utf-8") };
} catch {
console.log(`[Skill] ❌ 读取失败:未找到文件 "${relPath}"`);
return { error: `File not found: ${relPath}` };
}
},
@ -180,26 +186,25 @@ function createSkillTools(skills: SkillRecord[]) {
export async function useSkill(...segments: string[]) {
if (segments.length === 0) return { prompt: "", tools: {} };
const baseDir = path.join(getPath("skills"), ...segments);
const location = path.join(baseDir, "SKILL.md");
const skills = new Map<string, SkillRecord>();
let content: string;
try { content = await fs.readFile(location, "utf-8"); } catch {
console.log(`[Skill] ⚠️ 未发现技能:${segments.join("/")}`);
return { prompt: "", tools: {} };
const primary = await readSkillFromDir(path.join(getPath("skills"), ...segments));
if (primary) skills.set(primary.name, primary);
const publicDir = path.join(getPath("skills"), "public");
try {
const entries = await fs.readdir(publicDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const skill = await readSkillFromDir(path.join(publicDir, entry.name));
if (skill && !skills.has(skill.name)) skills.set(skill.name, skill);
}
} catch {
/* public dir not found */
}
let metadata: { name: string; description: string };
try { metadata = parseFrontmatter(content); } catch (e) {
console.log(`[Skill] ⚠️ 解析失败 "${segments.join("/")}"${(e as Error).message}`);
return { prompt: "", tools: {} };
}
if (skills.size === 0) return { prompt: "", tools: {} };
const skill: SkillRecord = { ...metadata, location, baseDir };
console.log(`[Skill] ✅ 发现技能:${skill.name}${skill.description}`);
return {
prompt: buildCatalog(skill),
tools: createSkillTools([skill]),
};
const allSkills = [...skills.values()];
return { prompt: buildCatalog(allSkills), tools: createSkillTools(allSkills) };
}