import { z } from 'zod'; import type { Artboard, InsertOrigin, IntentDiff } from '@originmain/origin-graph'; // ── Plugin Manifest ─────────────────────────────────────────────────────────── // Plugins declare their identity, permissions, and extension points in a // manifest. The host validates this on install and on every load. export const PluginPermissionSchema = z.enum([ 'artboards:read', // read artboard metadata and component trees 'artboards:write', // create artboards and origins (requires approval) 'diffs:read', // read IntentDiff objects 'diffs:export', // export diffs to external tools 'completion-zones:register', // register custom Completion Zone types 'ingesters:register', // register custom ingestion connectors 'design-language:read', // read the workspace Design Language File ]); export type PluginPermission = z.infer; export const PluginManifestSchema = z.object({ /** Unique reverse-DNS identifier: e.g. "com.acme.my-plugin" */ id: z.string().regex(/^[a-z0-9]+(\.[a-z0-9-]+)+$/, 'Must be reverse-DNS format'), name: z.string().min(1).max(80), version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Must be semver'), description: z.string().max(300), /** URL where the plugin's sandboxed JS bundle is hosted */ entryUrl: z.string().url(), permissions: z.array(PluginPermissionSchema), /** SHA-256 hash of the entryUrl bundle — required; verified before execution */ bundleHash: z.string().regex(/^[0-9a-f]{64}$/), }); export type PluginManifest = z.infer; // ── Plugin Context ──────────────────────────────────────────────────────────── // The API surface exposed to a sandboxed plugin via postMessage. // Split into read and write sides so permission checks are explicit. export interface PluginReadAPI { getArtboard(id: string): Promise; listArtboards(): Promise; getDiff(id: string): Promise; listPendingDiffs(): Promise; getDesignLanguageFile(): Promise; } export interface PluginWriteAPI { /** * Creates a new artboard linked to the given origin. * The host injects workspaceId from PluginContext.workspaceId — plugins * do not set workspace scoping; it is enforced server-side. */ createArtboard(params: { name: string; origin: InsertOrigin; }): Promise<{ artboardId: string }>; exportDiff(diffId: string, format: 'json' | 'markdown'): Promise; } export interface PluginContext { /** Plugin's declared manifest (read-only inside sandbox) */ readonly manifest: PluginManifest; /** Workspace ID the plugin is operating within */ readonly workspaceId: string; /** Read APIs — available if 'artboards:read' or 'diffs:read' is granted */ readonly read: PluginReadAPI; /** Write APIs — available only if 'artboards:write' is granted */ readonly write: PluginWriteAPI | null; /** Emits a UI notification to the host application */ notify(message: string, level?: 'info' | 'warning' | 'error'): void; } // ── Custom Completion Zone ──────────────────────────────────────────────────── // Plugins can register custom Completion Zone types with their own AI prompts // and rendering logic. export interface CustomCompletionZoneDefinition { /** Unique type identifier: e.g. "com.acme.marketing-copy" */ type: string; label: string; description: string; /** System prompt appended to the standard Completion Zone system prompt */ systemPromptAddendum: string; /** JSON Schema describing the expected output format */ outputSchema: Record; } // ── Custom Ingester ─────────────────────────────────────────────────────────── // Plugins can register additional ingestion connectors beyond the 4 first-party set. export interface CustomIngesterDefinition { /** Matches the 'type' field in the plugin manifest */ sourceType: string; label: string; /** Webhook URL path registered with the host's Edge Function router */ webhookPath: string; /** The plugin's JS bundle handles validatePayload + ingest via sandboxed eval */ handleWebhook(rawBody: string, headers: Record): Promise<{ artboardTitle: string; renderUrl?: string; sourceRef: string; metadata: Record; }>; } // ── Plugin Registry ─────────────────────────────────────────────────────────── export interface InstalledPlugin { manifest: PluginManifest; installedAt: string; installedByUserId: string; enabled: boolean; } export interface PluginRegistry { list(workspaceId: string): Promise; install(workspaceId: string, manifest: PluginManifest, byUserId: string): Promise; uninstall(workspaceId: string, pluginId: string): Promise; setEnabled(workspaceId: string, pluginId: string, enabled: boolean): Promise; }