fix(teams): return canonical links for saved catalog resources

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-03 22:14:50 -06:00
1 parent 2c8f1cf44e
commit ce293f7ac7
3 files changed
+21 -4

No files matched your search

@@ -182,6 +182,7 @@ export interface CatalogTeamImportPreviewResult {
}
export interface CatalogTeamInstallResult {
resourceLinks: { kind: "agent" | "project" | "routine"; id: string; name: string; href: string }[];
team: CatalogTeam;
portabilityImport: CompanyPortabilityImportResult;
skillPreparations: CatalogTeamSkillPreparation[];
+6 -3
View File
@@ -3103,9 +3103,12 @@ for (const route of [
path: route[1],
tags: ["teams"],
summary: route[2],
description: route[1].endsWith("/install")
? "Imports team resources; HTTP success does not prove every required skill is ready. Read warnings for post-import failures and skillPreparations for required sources; a preparation plan is not proof of installation. Report unresolved requirements as partial completion and preserve existing resources. Use the actual returned portabilityImport agents and projects for company-scoped resource links. Before repeating an uncertain install, inspect installed teams and saved resource identities rather than creating another copy."
: undefined,
...(route[1].endsWith("/install") ? {
description: "Imports team resources; HTTP success does not prove every required skill is ready. Read warnings for post-import failures and skillPreparations for required sources; a preparation plan is not proof of installation. Report unresolved requirements as partial completion and preserve existing resources. Use the actual returned portabilityImport agents and projects for company-scoped resource links. Before repeating an uncertain install, inspect installed teams and saved resource identities rather than creating another copy. resourceLinks provides canonical company-scoped hrefs for actual saved agents, projects and routines, including reused resources; share these links with the installation outcome.",
responses: { 200: r.ok(z.object({ resourceLinks: z.array(z.object({
kind: z.enum(["agent", "project", "routine"]), id: z.string(), name: z.string(), href: z.string(),
})) }).passthrough()), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound },
} : {}),
});
}
+14 -1
View File
@@ -1,10 +1,12 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Db } from "@paperclipai/db";
import { companies, type Db } from "@paperclipai/db";
import { eq } from "drizzle-orm";
import type {
CatalogManifest,
CatalogTeam,
CatalogTeamInstallResult as SharedCatalogTeamInstallResult,
CatalogTeamFileKind,
CatalogTeamKind,
CatalogTeamSkillRequirement,
@@ -105,6 +107,7 @@ export interface CatalogTeamImportPreviewResult {
}
export interface CatalogTeamInstallResult {
resourceLinks: SharedCatalogTeamInstallResult["resourceLinks"];
team: CatalogTeam;
portabilityImport: CompanyPortabilityImportResult;
skillPreparations: CatalogTeamSkillPreparation[];
@@ -904,6 +907,8 @@ export function teamsCatalogService(db: Db) {
catalogRef: string,
options: CatalogTeamImportOptions = {},
): Promise<CatalogTeamInstallResult> {
const [company] = await db.select({ issuePrefix: companies.issuePrefix }).from(companies).where(eq(companies.id, companyId)).limit(1);
if (!company) throw notFound("Company not found");
const prepared = await prepareCatalogTeamSource(companyId, catalogRef, options);
if (prepared.errors.length > 0) {
throw unprocessable(`Catalog team source preparation failed: ${prepared.errors.join("; ")}`);
@@ -957,6 +962,14 @@ export function teamsCatalogService(db: Db) {
return {
team: prepared.team,
portabilityImport: result,
resourceLinks: [
...result.agents.flatMap(agent => agent.id ? [{ kind: "agent" as const, id: agent.id, name: agent.name,
href: `/${encodeURIComponent(company.issuePrefix)}/agents/${encodeURIComponent(agent.id)}` }] : []),
...result.projects.flatMap(project => project.id ? [{ kind: "project" as const, id: project.id, name: project.name,
href: `/${encodeURIComponent(company.issuePrefix)}/projects/${encodeURIComponent(project.id)}` }] : []),
...(result.routines ?? []).flatMap(routine => routine.id ? [{ kind: "routine" as const, id: routine.id, name: routine.title,
href: `/${encodeURIComponent(company.issuePrefix)}/routines/${encodeURIComponent(routine.id)}` }] : []),
],
skillPreparations: prepared.skillPreparations,
warnings: result.warnings,
};