forked from farhoodlabs/paperclip
fix(skills): handle prune type mismatch by using warnings for agent-attached skills
GitHub/sks_sh pruned skills don't have project/workspace context needed for the CompanySkillProjectScanConflict/Skipped types. Orphaned skills are silently deleted; skills still used by agents emit a warning instead of a conflict entry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -278,31 +278,24 @@ describe("company skill mutation permissions", () => {
|
|||||||
expect(mockCompanySkillService.scanProjectWorkspaces).toHaveBeenCalledWith("company-1", {});
|
expect(mockCompanySkillService.scanProjectWorkspaces).toHaveBeenCalledWith("company-1", {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns skipped and conflicts from scan including pruned skill conflicts", async () => {
|
it("returns warnings from scan when removed skills are still used by agents", async () => {
|
||||||
mockAgentService.getById.mockResolvedValue({
|
mockAgentService.getById.mockResolvedValue({
|
||||||
id: "agent-1",
|
id: "agent-1",
|
||||||
companyId: "company-1",
|
companyId: "company-1",
|
||||||
permissions: { canCreateAgents: true },
|
permissions: { canCreateAgents: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const skippedSkill = { id: "skill-1", slug: "removed-skill", name: "Removed Skill" };
|
|
||||||
const conflictEntry = {
|
|
||||||
path: "https://github.com/vercel-labs/agent-browser",
|
|
||||||
existingSkillId: "skill-2",
|
|
||||||
existingSkillKey: "vercel-labs/agent-browser/ghost-skill",
|
|
||||||
existingSourceLocator: "https://github.com/vercel-labs/agent-browser",
|
|
||||||
reason: 'Skill "ghost-skill" was removed from https://github.com/vercel-labs/agent-browser but is still used by Builder. Detach it from those agents first.',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockCompanySkillService.scanProjectWorkspaces.mockResolvedValueOnce({
|
mockCompanySkillService.scanProjectWorkspaces.mockResolvedValueOnce({
|
||||||
scannedProjects: 1,
|
scannedProjects: 1,
|
||||||
scannedWorkspaces: 1,
|
scannedWorkspaces: 1,
|
||||||
discovered: [],
|
discovered: [],
|
||||||
imported: [],
|
imported: [],
|
||||||
updated: [],
|
updated: [],
|
||||||
skipped: [skippedSkill],
|
skipped: [],
|
||||||
conflicts: [conflictEntry],
|
conflicts: [],
|
||||||
warnings: [],
|
warnings: [
|
||||||
|
'Skill "ghost-skill" was removed from https://github.com/vercel-labs/agent-browser but is still used by Builder. It will not be automatically removed.',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await request(await createApp({
|
const res = await request(await createApp({
|
||||||
@@ -316,12 +309,7 @@ describe("company skill mutation permissions", () => {
|
|||||||
|
|
||||||
expect(res.status, JSON.stringify(res.body)).toBe(200);
|
expect(res.status, JSON.stringify(res.body)).toBe(200);
|
||||||
expect(res.body).toMatchObject({
|
expect(res.body).toMatchObject({
|
||||||
skipped: [{ id: "skill-1", slug: "removed-skill" }],
|
warnings: [expect.stringContaining("was removed from")],
|
||||||
conflicts: [{
|
|
||||||
path: "https://github.com/vercel-labs/agent-browser",
|
|
||||||
existingSkillId: "skill-2",
|
|
||||||
reason: expect.stringContaining("was removed from"),
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2044,16 +2044,11 @@ export function companySkillService(db: Db) {
|
|||||||
if (currentSlugs.has(skill.slug)) continue;
|
if (currentSlugs.has(skill.slug)) continue;
|
||||||
const usedByAgents = await usage(companyId, skill.key);
|
const usedByAgents = await usage(companyId, skill.key);
|
||||||
if (usedByAgents.length > 0) {
|
if (usedByAgents.length > 0) {
|
||||||
conflicts.push({
|
warnings.push(
|
||||||
path: sourceLocator,
|
`Skill "${skill.slug}" was removed from ${sourceLocator} but is still used by ${usedByAgents.map((a) => a.name).join(", ")}. It will not be automatically removed.`,
|
||||||
existingSkillId: skill.id,
|
);
|
||||||
existingSkillKey: skill.key,
|
|
||||||
existingSourceLocator: sourceLocator,
|
|
||||||
reason: `Skill "${skill.slug}" was removed from ${sourceLocator} but is still used by ${usedByAgents.map((a) => a.name).join(", ")}. Detach it from those agents first.`,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const deleted = await deleteSkill(companyId, skill.id);
|
await deleteSkill(companyId, skill.id);
|
||||||
if (deleted) skipped.push(deleted);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user