From 8460fee380be8a6ae1b30de3fdabf6ac33206597 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 17:45:28 -0500 Subject: [PATCH] Reduce company skill list payloads Co-Authored-By: Paperclip --- packages/shared/src/types/company-skill.ts | 15 +++++- .../src/__tests__/company-portability.test.ts | 7 ++- server/src/services/company-portability.ts | 2 +- server/src/services/company-skills.ts | 47 +++++++++++++++---- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/types/company-skill.ts b/packages/shared/src/types/company-skill.ts index 4476d1b2..c1338e0e 100644 --- a/packages/shared/src/types/company-skill.ts +++ b/packages/shared/src/types/company-skill.ts @@ -29,7 +29,20 @@ export interface CompanySkill { updatedAt: Date; } -export interface CompanySkillListItem extends CompanySkill { +export interface CompanySkillListItem { + id: string; + companyId: string; + slug: string; + name: string; + description: string | null; + sourceType: CompanySkillSourceType; + sourceLocator: string | null; + sourceRef: string | null; + trustLevel: CompanySkillTrustLevel; + compatibility: CompanySkillCompatibility; + fileInventory: CompanySkillFileInventoryEntry[]; + createdAt: Date; + updatedAt: Date; attachedAgentCount: number; editable: boolean; editableReason: string | null; diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index 67ade31d..cdbc4240 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -31,6 +31,7 @@ const issueSvc = { const companySkillSvc = { list: vi.fn(), + listFull: vi.fn(), readFile: vi.fn(), importPackageFiles: vi.fn(), }; @@ -148,7 +149,7 @@ describe("company portability", () => { issueSvc.list.mockResolvedValue([]); issueSvc.getById.mockResolvedValue(null); issueSvc.getByIdentifier.mockResolvedValue(null); - companySkillSvc.list.mockResolvedValue([ + const companySkills = [ { id: "skill-1", companyId: "company-1", @@ -194,7 +195,9 @@ describe("company portability", () => { sourceKind: "local_path", }, }, - ]); + ]; + companySkillSvc.list.mockResolvedValue(companySkills); + companySkillSvc.listFull.mockResolvedValue(companySkills); companySkillSvc.readFile.mockImplementation(async (_companyId: string, skillId: string, relativePath: string) => { if (skillId === "skill-2") { return { diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index e5e33d49..9d1fc48d 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -1551,7 +1551,7 @@ export function companyPortabilityService(db: Db) { const allAgentRows = include.agents ? await agents.list(companyId, { includeTerminated: true }) : []; const agentRows = allAgentRows.filter((agent) => agent.status !== "terminated"); - const companySkillRows = await companySkills.list(companyId); + const companySkillRows = await companySkills.listFull(companyId); if (include.agents) { const skipped = allAgentRows.length - agentRows.length; if (skipped > 0) { diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 1ba24db7..495a8e79 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -881,6 +881,30 @@ function enrichSkill(skill: CompanySkill, attachedAgentCount: number, usedByAgen }; } +function toCompanySkillListItem(skill: CompanySkill, attachedAgentCount: number): CompanySkillListItem { + const source = deriveSkillSourceInfo(skill); + return { + id: skill.id, + companyId: skill.companyId, + slug: skill.slug, + name: skill.name, + description: skill.description, + sourceType: skill.sourceType, + sourceLocator: skill.sourceLocator, + sourceRef: skill.sourceRef, + trustLevel: skill.trustLevel, + compatibility: skill.compatibility, + fileInventory: skill.fileInventory, + createdAt: skill.createdAt, + updatedAt: skill.updatedAt, + attachedAgentCount, + editable: source.editable, + editableReason: source.editableReason, + sourceLabel: source.sourceLabel, + sourceBadge: source.sourceBadge, + }; +} + export function companySkillService(db: Db) { const agents = agentService(db); const secretsSvc = secretService(db); @@ -905,21 +929,25 @@ export function companySkillService(db: Db) { } async function list(companyId: string): Promise { + const rows = await listFull(companyId); + const agentRows = await agents.list(companyId); + return rows.map((skill) => { + const attachedAgentCount = agentRows.filter((agent) => { + const preference = readPaperclipSkillSyncPreference(agent.adapterConfig as Record); + return preference.desiredSkills.includes(skill.slug); + }).length; + return toCompanySkillListItem(skill, attachedAgentCount); + }); + } + + async function listFull(companyId: string): Promise { await ensureBundledSkills(companyId); const rows = await db .select() .from(companySkills) .where(eq(companySkills.companyId, companyId)) .orderBy(asc(companySkills.name), asc(companySkills.slug)); - const agentRows = await agents.list(companyId); - return rows.map((row) => { - const skill = toCompanySkill(row); - const attachedAgentCount = agentRows.filter((agent) => { - const preference = readPaperclipSkillSyncPreference(agent.adapterConfig as Record); - return preference.desiredSkills.includes(skill.slug); - }).length; - return enrichSkill(skill, attachedAgentCount); - }); + return rows.map((row) => toCompanySkill(row)); } async function getById(id: string) { @@ -1375,6 +1403,7 @@ export function companySkillService(db: Db) { return { list, + listFull, getById, getBySlug, detail,