refactor(*): 稳定期治理 - 收敛核心架构、错误语义并修复跨项目数据竞态

This commit is contained in:
ddCat
2026-09-09 22:02:10 +08:00
parent bacd502f5c
commit b5f3413ded
298 changed files with 12681 additions and 26428 deletions

View File

@@ -138,10 +138,10 @@ const PROGRESSIVE_SKILL_TOOLS: ResponseFunctionTool[] = [
];
export function resolveSkillMentions(prompt: string, skills: Skill[], selectedSkillIds?: string[]) {
const activeSkills = skills.filter((skill) => skill.is_added);
const activeSkills = skills.filter((skill) => skill.isAdded);
if (!activeSkills.length) return [];
if (selectedSkillIds) {
const byId = new Map(activeSkills.map((skill) => [skill.skill_id, skill]));
const byId = new Map(activeSkills.map((skill) => [skill.skillId, skill]));
return Array.from(new Set(selectedSkillIds)).flatMap((id) => {
const skill = byId.get(id);
return skill ? [skill] : [];
@@ -153,18 +153,18 @@ export function resolveSkillMentions(prompt: string, skills: Skill[], selectedSk
let match: RegExpExecArray | null;
SKILL_REF_PATTERN.lastIndex = 0;
while ((match = SKILL_REF_PATTERN.exec(prompt))) mentionedIds.add(match[1]);
return activeSkills.filter((skill) => mentionedIds.has(skill.skill_id) || containsNaturalSkillMention(prompt, skill.skill_name));
return activeSkills.filter((skill) => mentionedIds.has(skill.skillId) || containsNaturalSkillMention(prompt, skill.skillName));
}
export function buildSkillMentionReferences(skills: Skill[]): CanvasResourceReference[] {
return skills
.filter((skill) => skill.is_added)
.filter((skill) => skill.isAdded)
.map((skill) => ({
id: `skill:${skill.skill_id}`,
nodeId: `skill:${skill.skill_id}`,
id: `skill:${skill.skillId}`,
nodeId: `skill:${skill.skillId}`,
kind: "skill" as const,
label: skill.skill_name,
title: skill.skill_name,
label: skill.skillName,
title: skill.skillName,
text: skill.description,
active: true,
skill,
@@ -240,8 +240,8 @@ async function prepareLinkedContext(input: PreparedSkillInput, dependencies: Ski
}
async function loadLinkedSkill(skill: Skill, prompt: string, budget: number, maxLinkedFiles: number, dependencies: SkillRuntimeDependencies) {
const [entryResult, fileList] = await Promise.all([dependencies.getFile(skill.skill_id, "SKILL.md"), dependencies.listFiles(skill.skill_id)]);
if (entryResult.file.binary) throw new Error(`技能「${skill.skill_name}」的 SKILL.md 不是文本文件`);
const [entryResult, fileList] = await Promise.all([dependencies.getFile(skill.skillId, "SKILL.md"), dependencies.listFiles(skill.skillId)]);
if (entryResult.file.binary) throw new Error(`技能「${skill.skillName}」的 SKILL.md 不是文本文件`);
let remaining = budget;
const entryContent = boundedText(entryResult.file.content, remaining);
@@ -250,7 +250,7 @@ async function loadLinkedSkill(skill: Skill, prompt: string, budget: number, max
if (remaining <= 0 || maxLinkedFiles <= 0) return { skill, files };
const candidates = linkedFileCandidates(entryResult.file.content, fileList.files, prompt).slice(0, maxLinkedFiles);
const linkedFiles = await Promise.all(candidates.map((candidate) => dependencies.getFile(skill.skill_id, candidate.path)));
const linkedFiles = await Promise.all(candidates.map((candidate) => dependencies.getFile(skill.skillId, candidate.path)));
for (let index = 0; index < candidates.length; index += 1) {
if (remaining <= 0) break;
const candidate = candidates[index];
@@ -311,7 +311,7 @@ function boundedText(value: string, maxChars: number) {
}
function renderLinkedSkillContext(skill: Skill, files: Array<{ path: string; content: string }>) {
const attributes = `skill-id="${escapeAttribute(skill.skill_id)}" name="${escapeAttribute(skill.skill_name)}" version="${escapeAttribute(skill.version)}"`;
const attributes = `skill-id="${escapeAttribute(skill.skillId)}" name="${escapeAttribute(skill.skillName)}" version="${escapeAttribute(skill.version)}"`;
const body = files.map((file) => `<skill-file path="${escapeAttribute(file.path)}">\n${file.content}\n</skill-file>`).join("\n\n");
return `<skill-context ${attributes}>\n${body}\n</skill-context>`;
}
@@ -321,54 +321,54 @@ function linkedResult(prompt: string, selectedSkills: Skill[], provenance: Skill
}
async function prepareNativePackages(input: PreparedSkillInput, dependencies: SkillRuntimeDependencies): Promise<NativeSkillRuntimeResult> {
const bundles = await Promise.all(input.selectedSkills.map((skill) => dependencies.getBundle(skill.skill_id)));
const bundles = await Promise.all(input.selectedSkills.map((skill) => dependencies.getBundle(skill.skillId)));
const skills = bundles.map((result, index) => nativePackage(input.selectedSkills[index], result.bundle));
const provenance = {
skillIds: input.selectedSkills.map((skill) => skill.skill_id),
skillVersions: input.selectedSkills.map((skill, index) => ({ skillId: skill.skill_id, versionId: bundles[index].bundle.version_id, version: bundles[index].bundle.version })),
skillFiles: bundles.flatMap((result, index) => result.bundle.files.map((file) => ({ skillId: input.selectedSkills[index].skill_id, path: file.path }))),
skillIds: input.selectedSkills.map((skill) => skill.skillId),
skillVersions: input.selectedSkills.map((skill, index) => ({ skillId: skill.skillId, versionId: bundles[index].bundle.versionId, version: bundles[index].bundle.version })),
skillFiles: bundles.flatMap((result, index) => result.bundle.files.map((file) => ({ skillId: input.selectedSkills[index].skillId, path: file.path }))),
};
return { delivery: "native-package", prompt: input.prompt, selectedSkills: input.selectedSkills, skills, provenance, metadata: skillRuntimeMetadata(provenance) };
}
function nativePackage(skill: Skill, bundle: SkillPackageBundle): NativeSkillPackage {
return {
skillId: skill.skill_id,
name: skill.skill_name,
skillId: skill.skillId,
name: skill.skillName,
description: skill.description,
version: bundle.version,
files: bundle.files.map((file) => ({ path: file.path, mimeType: file.mime_type, contentBase64: file.content_base64 })),
files: bundle.files.map((file) => ({ path: file.path, mimeType: file.mimeType, contentBase64: file.contentBase64 })),
};
}
function createProgressiveToolsAdapter(dependencies: SkillRuntimeDependencies): SkillToolAdapter {
const handlers: Record<string, (args: Record<string, unknown>, skills: Skill[]) => Promise<SkillRuntimeToolResult>> = {
canvas_list_skills: async (_args, skills) => {
const data = skills.filter((skill) => skill.is_added).map((skill) => ({ skillId: skill.skill_id, name: skill.skill_name, description: skill.description, tag: skill.tag, version: skill.version, fileCount: skill.file_count, sourceType: skill.source_type }));
const data = skills.filter((skill) => skill.isAdded).map((skill) => ({ skillId: skill.skillId, name: skill.skillName, description: skill.description, tag: skill.tag, version: skill.version, fileCount: skill.fileCount, sourceType: skill.sourceType }));
return { ok: true, message: data.length ? "已列出当前可用技能。" : "当前没有已加入技能。", data };
},
canvas_get_skill: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const entry = await dependencies.getFile(skill.skill_id, "SKILL.md");
return { ok: true, message: `已读取技能「${skill.skill_name}」的入口 SKILL.md;如入口引用其他文件,请继续按需读取。`, data: { skillId: skill.skill_id, name: skill.skill_name, description: skill.description, version: skill.version, fileCount: skill.file_count, sourceType: skill.source_type, entry: entry.file } };
const entry = await dependencies.getFile(skill.skillId, "SKILL.md");
return { ok: true, message: `已读取技能「${skill.skillName}」的入口 SKILL.md;如入口引用其他文件,请继续按需读取。`, data: { skillId: skill.skillId, name: skill.skillName, description: skill.description, version: skill.version, fileCount: skill.fileCount, sourceType: skill.sourceType, entry: entry.file } };
},
canvas_list_skill_files: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const result = await dependencies.listFiles(skill.skill_id);
return { ok: true, message: `已列出技能「${skill.skill_name}」的 ${result.files.length} 个文件,仅含元数据。`, data: { skillId: skill.skill_id, files: result.files } };
const result = await dependencies.listFiles(skill.skillId);
return { ok: true, message: `已列出技能「${skill.skillName}」的 ${result.files.length} 个文件,仅含元数据。`, data: { skillId: skill.skillId, files: result.files } };
},
canvas_read_skill_file: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const path = requireString(args.path, "path");
const result = await dependencies.getFile(skill.skill_id, path);
const result = await dependencies.getFile(skill.skillId, path);
if (result.file.binary) return { ok: false, message: `文件 ${path} 不是可读取的文本文件。` };
return { ok: true, message: `已按需读取 ${path}。`, data: { skillId: skill.skill_id, file: result.file } };
return { ok: true, message: `已按需读取 ${path}。`, data: { skillId: skill.skillId, file: result.file } };
},
canvas_search_skill_files: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const query = requireString(args.query, "query");
const result = await dependencies.searchFiles(skill.skill_id, query);
return { ok: true, message: result.results.length ? `在技能「${skill.skill_name}」中找到 ${result.results.length} 处匹配。` : "技能包中没有匹配内容。", data: { skillId: skill.skill_id, query, results: result.results } };
const result = await dependencies.searchFiles(skill.skillId, query);
return { ok: true, message: result.results.length ? `在技能「${skill.skillName}」中找到 ${result.results.length} 处匹配。` : "技能包中没有匹配内容。", data: { skillId: skill.skillId, query, results: result.results } };
},
};
return {
@@ -388,17 +388,17 @@ function createProgressiveToolsAdapter(dependencies: SkillRuntimeDependencies):
function provenanceFromLoaded(loaded: Array<{ skill: Skill; files: Array<{ path: string; sha256?: string }> }>): SkillRuntimeProvenance {
return {
skillIds: loaded.map((item) => item.skill.skill_id),
skillVersions: loaded.map((item) => ({ skillId: item.skill.skill_id, versionId: item.skill.version_id, version: item.skill.version })),
skillFiles: loaded.flatMap((item) => item.files.map((file) => ({ skillId: item.skill.skill_id, path: file.path, sha256: file.sha256 }))),
skillIds: loaded.map((item) => item.skill.skillId),
skillVersions: loaded.map((item) => ({ skillId: item.skill.skillId, versionId: item.skill.versionId, version: item.skill.version })),
skillFiles: loaded.flatMap((item) => item.files.map((file) => ({ skillId: item.skill.skillId, path: file.path, sha256: file.sha256 }))),
};
}
function normalizeSkillTokens(prompt: string, skills: Skill[]) {
const byId = new Map(skills.map((skill) => [skill.skill_id, skill]));
const byId = new Map(skills.map((skill) => [skill.skillId, skill]));
return prompt.replace(SKILL_REF_PATTERN, (token, id) => {
const skill = byId.get(id);
return skill ? `@${skill.skill_name}` : token;
return skill ? `@${skill.skillName}` : token;
});
}
@@ -431,7 +431,7 @@ function escapeAttribute(value: string) {
function requireAddedSkill(skills: Skill[], args: Record<string, unknown>) {
const skillId = typeof args.skillId === "string" ? args.skillId.trim() : "";
const name = typeof args.name === "string" ? args.name.trim().toLocaleLowerCase() : "";
const skill = skills.find((item) => item.is_added && (item.skill_id === skillId || (name && item.skill_name.toLocaleLowerCase() === name)));
const skill = skills.find((item) => item.isAdded && (item.skillId === skillId || (name && item.skillName.toLocaleLowerCase() === name)));
if (!skill) throw new Error("未找到已加入的技能,请先调用 canvas_list_skills。");
return skill;
}