mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
feat(adapters): external adapter plugin system with dynamic UI parser
- Plugin loader: install/reload/remove/reinstall external adapters from npm packages or local directories - Plugin store persisted at ~/.paperclip/adapter-plugins.json - Self-healing UI parser resolution with version caching - UI: Adapter Manager page, dynamic loader, display registry with humanized names for unknown adapter types - Dev watch: exclude adapter-plugins dir from tsx watcher to prevent mid-request server restarts during reinstall - All consumer fallbacks use getAdapterLabel() for consistent display - AdapterTypeDropdown uses controlled open state for proper close behavior - Remove hermes-local from built-in UI (externalized to plugin) - Add docs for external adapters and UI parser contract
This commit is contained in:
parent
f8452a4520
commit
14d59da316
72 changed files with 4102 additions and 585 deletions
15
packages/shared/src/adapter-type.ts
Normal file
15
packages/shared/src/adapter-type.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { z } from "zod";
|
||||
import { AGENT_ADAPTER_TYPES } from "./constants.js";
|
||||
|
||||
export const agentAdapterTypeSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.default("process")
|
||||
.describe(`Known built-in adapters: ${AGENT_ADAPTER_TYPES.join(", ")}. External adapters may register additional non-empty string types at runtime.`);
|
||||
|
||||
export const optionalAgentAdapterTypeSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.optional();
|
||||
38
packages/shared/src/adapter-types.test.ts
Normal file
38
packages/shared/src/adapter-types.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { acceptInviteSchema, createAgentSchema, updateAgentSchema } from "./index.js";
|
||||
|
||||
describe("dynamic adapter type validation schemas", () => {
|
||||
it("accepts external adapter types in create/update agent schemas", () => {
|
||||
expect(
|
||||
createAgentSchema.parse({
|
||||
name: "External Agent",
|
||||
adapterType: "external_adapter",
|
||||
}).adapterType,
|
||||
).toBe("external_adapter");
|
||||
|
||||
expect(
|
||||
updateAgentSchema.parse({
|
||||
adapterType: "external_adapter",
|
||||
}).adapterType,
|
||||
).toBe("external_adapter");
|
||||
});
|
||||
|
||||
it("still rejects blank adapter types", () => {
|
||||
expect(() =>
|
||||
createAgentSchema.parse({
|
||||
name: "Blank Adapter",
|
||||
adapterType: " ",
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("accepts external adapter types in invite acceptance schema", () => {
|
||||
expect(
|
||||
acceptInviteSchema.parse({
|
||||
requestType: "agent",
|
||||
agentName: "External Joiner",
|
||||
adapterType: "external_adapter",
|
||||
}).adapterType,
|
||||
).toBe("external_adapter");
|
||||
});
|
||||
});
|
||||
|
|
@ -31,9 +31,8 @@ export const AGENT_ADAPTER_TYPES = [
|
|||
"pi_local",
|
||||
"cursor",
|
||||
"openclaw_gateway",
|
||||
"hermes_local",
|
||||
] as const;
|
||||
export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number];
|
||||
export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number] | (string & {});
|
||||
|
||||
export const AGENT_ROLES = [
|
||||
"ceo",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { agentAdapterTypeSchema, optionalAgentAdapterTypeSchema } from "./adapter-type.js";
|
||||
export {
|
||||
COMPANY_STATUSES,
|
||||
DEPLOYMENT_MODES,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { z } from "zod";
|
||||
import {
|
||||
AGENT_ADAPTER_TYPES,
|
||||
INVITE_JOIN_TYPES,
|
||||
JOIN_REQUEST_STATUSES,
|
||||
JOIN_REQUEST_TYPES,
|
||||
PERMISSION_KEYS,
|
||||
} from "../constants.js";
|
||||
import { optionalAgentAdapterTypeSchema } from "../adapter-type.js";
|
||||
|
||||
export const createCompanyInviteSchema = z.object({
|
||||
allowedJoinTypes: z.enum(INVITE_JOIN_TYPES).default("both"),
|
||||
|
|
@ -26,7 +26,7 @@ export type CreateOpenClawInvitePrompt = z.infer<
|
|||
export const acceptInviteSchema = z.object({
|
||||
requestType: z.enum(JOIN_REQUEST_TYPES),
|
||||
agentName: z.string().min(1).max(120).optional(),
|
||||
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional(),
|
||||
adapterType: optionalAgentAdapterTypeSchema,
|
||||
capabilities: z.string().max(4000).optional().nullable(),
|
||||
agentDefaultsPayload: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
// OpenClaw join compatibility fields accepted at top level.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { z } from "zod";
|
||||
import {
|
||||
AGENT_ADAPTER_TYPES,
|
||||
AGENT_ICON_NAMES,
|
||||
AGENT_ROLES,
|
||||
AGENT_STATUSES,
|
||||
INBOX_MINE_ISSUE_STATUS_FILTER,
|
||||
} from "../constants.js";
|
||||
import { agentAdapterTypeSchema } from "../adapter-type.js";
|
||||
import { envConfigSchema } from "./secret.js";
|
||||
|
||||
export const agentPermissionsSchema = z.object({
|
||||
|
|
@ -52,7 +52,7 @@ export const createAgentSchema = z.object({
|
|||
reportsTo: z.string().uuid().optional().nullable(),
|
||||
capabilities: z.string().optional().nullable(),
|
||||
desiredSkills: z.array(z.string().min(1)).optional(),
|
||||
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional().default("process"),
|
||||
adapterType: agentAdapterTypeSchema,
|
||||
adapterConfig: adapterConfigSchema.optional().default({}),
|
||||
runtimeConfig: z.record(z.unknown()).optional().default({}),
|
||||
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue