diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 5ddc731bf..13a805195 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -181,12 +181,40 @@ const DefaultPluginsSchema = z.object({ worker: RoleAgentDefaultsSchema, }); +const InstalledPluginConfigSchema = z + .object({ + name: z.string(), + source: z.enum(["registry", "npm", "local"]), + package: z.string().optional(), + version: z.string().optional(), + path: z.string().optional(), + enabled: z.boolean().default(true), + }) + .superRefine((value, ctx) => { + if (value.source === "local" && !value.path) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["path"], + message: "Local plugins require a path", + }); + } + + if ((value.source === "registry" || value.source === "npm") && !value.package) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["package"], + message: "Registry and npm plugins require a package name", + }); + } + }); + const OrchestratorConfigSchema = z.object({ port: z.number().default(3000), terminalPort: z.number().optional(), directTerminalPort: z.number().optional(), readyThresholdMs: z.number().nonnegative().default(300_000), defaults: DefaultPluginsSchema.default({}), + plugins: z.array(InstalledPluginConfigSchema).default([]), projects: z.record(ProjectConfigSchema), notifiers: z.record(NotifierConfigSchema).default({}), notificationRouting: z.record(z.array(z.string())).default({ @@ -216,6 +244,12 @@ function expandPaths(config: OrchestratorConfig): OrchestratorConfig { project.path = expandHome(project.path); } + for (const plugin of config.plugins ?? []) { + if (plugin.path) { + plugin.path = expandHome(plugin.path); + } + } + return config; } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 1a6caefcb..2431511a0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -971,6 +971,9 @@ export interface OrchestratorConfig { /** Default plugin selections */ defaults: DefaultPlugins; + /** Installer-managed external plugin descriptors */ + plugins?: InstalledPluginConfig[]; + /** Project configurations */ projects: Record; @@ -997,6 +1000,28 @@ export interface DefaultPlugins { }; } +export type InstalledPluginSource = "registry" | "npm" | "local"; + +export interface InstalledPluginConfig { + /** Stable logical plugin name used in config and CLI UX */ + name: string; + + /** Where the plugin should be resolved from */ + source: InstalledPluginSource; + + /** Package name for registry/npm-managed plugins */ + package?: string; + + /** Requested version/range for installer-managed plugins */ + version?: string; + + /** Filesystem path for local plugins */ + path?: string; + + /** Installer-managed enable flag (defaults to true) */ + enabled?: boolean; +} + export interface RoleAgentConfig { agent?: string; agentConfig?: AgentSpecificConfig;