Reduce company skill list payloads

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-16 17:45:28 -05:00
parent cca086b863
commit 8460fee380
4 changed files with 58 additions and 13 deletions

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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<CompanySkillListItem[]> {
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<string, unknown>);
return preference.desiredSkills.includes(skill.slug);
}).length;
return toCompanySkillListItem(skill, attachedAgentCount);
});
}
async function listFull(companyId: string): Promise<CompanySkill[]> {
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<string, unknown>);
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,