mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
merge: integrate agent lifecycle ownership from master
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
ad5d8826ad
159 files changed
+7173
-2805
No files matched your search
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { updateAgentSchema } from "./agent.js";
|
||||
|
||||
describe("agent update schema", () => {
|
||||
it.each(["paused", "idle", "terminated"])("accepts lifecycle status request %s", status => {
|
||||
expect(updateAgentSchema.parse({ status })).toEqual({ status });
|
||||
});
|
||||
it.each(["active", "running", "error", "pending_approval"])("rejects direct status write %s", status => {
|
||||
const result = updateAgentSchema.safeParse({ name: "Renamed", status });
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) expect(result.error.issues).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ path: ["status"] }),
|
||||
]));
|
||||
});
|
||||
it.each([{ spentMonthlyCents: 0 }, { name: "Renamed", spentMonthlyCents: 10 }])("rejects spend totals: %j", payload => {
|
||||
const result = updateAgentSchema.safeParse(payload);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) expect(result.error.issues).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ path: ["spentMonthlyCents"] }),
|
||||
]));
|
||||
});
|
||||
it("accepts profile and budget limit changes", () => {
|
||||
expect(updateAgentSchema.parse({ name: "Renamed", budgetMonthlyCents: 100 })).toEqual({ name: "Renamed", budgetMonthlyCents: 100 });
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { z } from "zod";
|
||||
import {
|
||||
AGENT_ICON_NAMES,
|
||||
AGENT_ROLES,
|
||||
AGENT_STATUSES,
|
||||
INBOX_MINE_ISSUE_STATUS_FILTER,
|
||||
} from "../constants.js";
|
||||
import { agentAdapterTypeSchema } from "../adapter-type.js";
|
||||
@@ -157,8 +156,8 @@ export const updateAgentSchema = objectWithoutDefaults(
|
||||
.extend({
|
||||
permissions: z.never().optional(),
|
||||
replaceAdapterConfig: z.boolean().optional(),
|
||||
status: z.enum(AGENT_STATUSES).optional(),
|
||||
spentMonthlyCents: z.number().int().nonnegative().optional(),
|
||||
status: z.enum(["paused", "idle", "terminated"]).optional(),
|
||||
spentMonthlyCents: z.never().optional(),
|
||||
});
|
||||
|
||||
export type UpdateAgent = z.infer<typeof updateAgentSchema>;
|
||||
|
||||
@@ -33,6 +33,12 @@ describe("plugin capability constants", () => {
|
||||
});
|
||||
|
||||
describe("plugin manifest validators", () => {
|
||||
it("requires authority for required agent lifecycle participants", () => {
|
||||
const manifest = { id: "example.lifecycle", apiVersion: 1, version: "0.1.0", displayName: "Lifecycle", description: "Lifecycle tests", author: "Tests", categories: ["automation"], entrypoints: { worker: "worker.js" }, agentLifecycle: true };
|
||||
expect(pluginManifestV1Schema.safeParse({ ...manifest, capabilities: ["agents.read"] }).success).toBe(false);
|
||||
expect(pluginManifestV1Schema.parse({ ...manifest, capabilities: ["agents.lifecycle.manage"] }).agentLifecycle).toBe(true);
|
||||
});
|
||||
|
||||
it("requires routing authority for native pooled connector declarations without a custom UI bundle", () => {
|
||||
const manifest = { id: "example.pool", apiVersion: 1, version: "0.1.0", displayName: "Pool", description: "Pool", author: "Tests", categories: ["connector"], entrypoints: { worker: "worker.js" }, aiConnectionRouter: { name: "AI connection pool", description: "Use saved connections" } };
|
||||
expect(pluginManifestV1Schema.safeParse({ ...manifest, capabilities: ["ui.page.register"] }).success).toBe(false);
|
||||
|
||||
@@ -782,6 +782,7 @@ export const pluginManifestV1Schema = z.object({
|
||||
"minimumPaperclipVersion must follow semver (e.g. 1.0.0)",
|
||||
).optional(),
|
||||
capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)).min(1),
|
||||
agentLifecycle: z.literal(true).optional(),
|
||||
aiConnectionRouter: z.object({ name: z.string().trim().min(1).max(100), description: z.string().trim().min(1).max(500) }).strict().optional(),
|
||||
entrypoints: z.object({
|
||||
worker: z.string().min(1),
|
||||
@@ -806,6 +807,10 @@ export const pluginManifestV1Schema = z.object({
|
||||
launchers: z.array(pluginLauncherDeclarationSchema).optional(),
|
||||
}).optional(),
|
||||
}).superRefine((manifest, ctx) => {
|
||||
if (manifest.agentLifecycle && !manifest.capabilities.includes("agents.lifecycle.manage")) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent lifecycle participation requires agents.lifecycle.manage", path: ["capabilities"] });
|
||||
}
|
||||
|
||||
// ── Entrypoint ↔ UI slot consistency ──────────────────────────────────
|
||||
// Plugins that declare UI slots must also declare a UI entrypoint so the
|
||||
// host knows where to load the bundle from (PLUGIN_SPEC.md §10.1).
|
||||
|
||||
Reference in new issue
Block a user