mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
Add managed plugin skill contracts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d0e9cc76f2
commit
268673e84c
17 files changed
+918
-24
No files matched your search
@@ -151,6 +151,7 @@ export const pluginManagedAgentDeclarationSchema = z.object({
|
||||
instructions: z.object({
|
||||
entryFile: z.string().min(1).max(200).optional(),
|
||||
content: z.string().max(200_000).optional(),
|
||||
files: z.record(z.string().max(200_000)).optional(),
|
||||
assetPath: z.string().min(1).max(500).optional(),
|
||||
}).optional(),
|
||||
});
|
||||
@@ -172,7 +173,7 @@ export type PluginManagedProjectDeclarationInput = z.infer<typeof pluginManagedP
|
||||
|
||||
const pluginManagedResourceRefSchema = z.object({
|
||||
pluginKey: z.string().min(1).max(100).optional(),
|
||||
resourceKind: z.enum(["agent", "project", "routine"]),
|
||||
resourceKind: z.enum(["agent", "project", "routine", "skill"]),
|
||||
resourceKey: z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9._:-]*$/, {
|
||||
message: "resourceKey must start with a lowercase alphanumeric and contain only lowercase letters, digits, dots, colons, underscores, or hyphens",
|
||||
}),
|
||||
@@ -232,6 +233,41 @@ export const pluginLocalFolderDeclarationSchema = z.object({
|
||||
|
||||
export type PluginLocalFolderDeclarationInput = z.infer<typeof pluginLocalFolderDeclarationSchema>;
|
||||
|
||||
export const pluginManagedSkillFileDeclarationSchema = z.object({
|
||||
path: pluginLocalFolderRelativePathSchema.refine(
|
||||
(value) => value.toLowerCase() !== "skill.md",
|
||||
{ message: "managed skill files cannot replace SKILL.md; use markdown for the main skill file" },
|
||||
),
|
||||
content: z.string().max(200_000),
|
||||
});
|
||||
|
||||
export type PluginManagedSkillFileDeclarationInput = z.infer<typeof pluginManagedSkillFileDeclarationSchema>;
|
||||
|
||||
export const pluginManagedSkillDeclarationSchema = z.object({
|
||||
skillKey: z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9._:-]*$/, {
|
||||
message: "skillKey must start with a lowercase alphanumeric and contain only lowercase letters, digits, dots, colons, underscores, or hyphens",
|
||||
}),
|
||||
displayName: z.string().min(1).max(100),
|
||||
slug: z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9._:-]*$/, {
|
||||
message: "slug must start with a lowercase alphanumeric and contain only lowercase letters, digits, dots, colons, underscores, or hyphens",
|
||||
}).optional(),
|
||||
description: z.string().max(2000).nullable().optional(),
|
||||
markdown: z.string().max(200_000).optional(),
|
||||
files: z.array(pluginManagedSkillFileDeclarationSchema).max(50).optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
const paths = (value.files ?? []).map((file) => file.path);
|
||||
const duplicates = paths.filter((path, index) => paths.indexOf(path) !== index);
|
||||
if (duplicates.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate managed skill file paths: ${[...new Set(duplicates)].join(", ")}`,
|
||||
path: ["files"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type PluginManagedSkillDeclarationInput = z.infer<typeof pluginManagedSkillDeclarationSchema>;
|
||||
|
||||
/**
|
||||
* Validates a {@link PluginUiSlotDeclaration} — a UI extension slot the plugin
|
||||
* fills with a React component. Includes `superRefine` checks for slot-specific
|
||||
@@ -589,6 +625,7 @@ export const pluginManifestV1Schema = z.object({
|
||||
agents: z.array(pluginManagedAgentDeclarationSchema).optional(),
|
||||
projects: z.array(pluginManagedProjectDeclarationSchema).optional(),
|
||||
routines: z.array(pluginManagedRoutineDeclarationSchema).optional(),
|
||||
skills: z.array(pluginManagedSkillDeclarationSchema).optional(),
|
||||
localFolders: z.array(pluginLocalFolderDeclarationSchema).optional(),
|
||||
launchers: z.array(pluginLauncherDeclarationSchema).optional(),
|
||||
ui: z.object({
|
||||
@@ -678,6 +715,16 @@ export const pluginManifestV1Schema = z.object({
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.skills && manifest.skills.length > 0) {
|
||||
if (!manifest.capabilities.includes("skills.managed")) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Capability 'skills.managed' is required when managed skills are declared",
|
||||
path: ["capabilities"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.localFolders && manifest.localFolders.length > 0) {
|
||||
if (!manifest.capabilities.includes("local.folders")) {
|
||||
ctx.addIssue({
|
||||
@@ -871,6 +918,18 @@ export const pluginManifestV1Schema = z.object({
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.skills) {
|
||||
const skillKeys = manifest.skills.map((skill) => skill.skillKey);
|
||||
const duplicates = skillKeys.filter((key, i) => skillKeys.indexOf(key) !== i);
|
||||
if (duplicates.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate managed skill keys: ${[...new Set(duplicates)].join(", ")}`,
|
||||
path: ["skills"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// UI slot ids must be unique within the plugin (namespaced at runtime)
|
||||
if (manifest.ui) {
|
||||
if (manifest.ui.slots) {
|
||||
|
||||
Reference in new issue
Block a user