From 5874f77f2cc6635af5a7c8427b67df6a346e0815 Mon Sep 17 00:00:00 2001 From: Marsh Macy Date: Mon, 14 Jul 2025 13:31:58 -0700 Subject: [PATCH 1/2] Add plugin-style modular role system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create comprehensive plugin architecture with role interfaces - Implement PluginManagerService for plugin and workflow management - Add built-in plugins for Technical Writer and Technical Reviewer - Create PluginManagerTab for visual plugin workflow configuration - Support plugin registration, validation, and execution - Include workflow execution engine with hooks and error handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- components/settings/PluginManagerTab.tsx | 838 +++++++++++++++++++++++ pages/SettingsPage.tsx | 18 +- services/pluginManagerService.ts | 458 +++++++++++++ types/rolePlugin.ts | 459 +++++++++++++ 4 files changed, 1772 insertions(+), 1 deletion(-) create mode 100644 components/settings/PluginManagerTab.tsx create mode 100644 services/pluginManagerService.ts create mode 100644 types/rolePlugin.ts diff --git a/components/settings/PluginManagerTab.tsx b/components/settings/PluginManagerTab.tsx new file mode 100644 index 0000000..c9e911a --- /dev/null +++ b/components/settings/PluginManagerTab.tsx @@ -0,0 +1,838 @@ +import React, { useState, useEffect } from 'react'; +import { + PluginWorkflowProfile, + PluginRoleInstance, + RolePlugin, + ConfigField +} from '../../types/rolePlugin'; +import { PluginManagerService } from '../../services/pluginManagerService'; + +interface PluginManagerTabProps { + disabled?: boolean; +} + +const PluginManagerTab: React.FC = ({ disabled = false }) => { + const [profiles, setProfiles] = useState([]); + const [selectedProfile, setSelectedProfile] = useState(null); + const [editingProfile, setEditingProfile] = useState(null); + const [editingRole, setEditingRole] = useState(null); + const [availablePlugins, setAvailablePlugins] = useState([]); + const [showRoleModal, setShowRoleModal] = useState(false); + const [showPluginModal, setShowPluginModal] = useState(false); + const [selectedPlugin, setSelectedPlugin] = useState(null); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [showImportModal, setShowImportModal] = useState(false); + const [importData, setImportData] = useState(''); + + const service = PluginManagerService.getInstance(); + + useEffect(() => { + loadData(); + }, []); + + const loadData = () => { + try { + const loadedProfiles = service.getAllProfiles(); + const plugins = service.getAllPlugins(); + + setProfiles(loadedProfiles); + setAvailablePlugins(plugins); + + if (!selectedProfile && loadedProfiles.length > 0) { + setSelectedProfile(loadedProfiles[0]); + } + + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load data'); + } + }; + + const handleCreateProfile = () => { + const newProfile: PluginWorkflowProfile = { + id: `plugin-profile-${Date.now()}`, + name: 'New Plugin Workflow', + description: 'Custom plugin-based workflow', + version: '1.0.0', + roles: [], + executionOrder: [], + globalSettings: { + maxTotalIterations: 15, + timeoutMinutes: 30, + allowParallelExecution: false + }, + metadata: { + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + author: 'User', + tags: [] + } + }; + setEditingProfile(newProfile); + }; + + const handleEditProfile = (profile: PluginWorkflowProfile) => { + setEditingProfile({ ...profile }); + }; + + const handleSaveProfile = () => { + if (!editingProfile) return; + + try { + if (profiles.some(p => p.id === editingProfile.id && p !== selectedProfile)) { + service.addProfile(editingProfile); + setSuccess('Profile created successfully'); + } else { + service.updateProfile(editingProfile.id, editingProfile); + setSuccess('Profile updated successfully'); + } + + loadData(); + setSelectedProfile(editingProfile); + setEditingProfile(null); + setError(null); + + // Clear success message after 3 seconds + setTimeout(() => setSuccess(null), 3000); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to save profile'); + } + }; + + const handleDeleteProfile = (profileId: string) => { + try { + service.deleteProfile(profileId); + loadData(); + if (selectedProfile?.id === profileId) { + setSelectedProfile(profiles.find(p => p.id !== profileId) || null); + } + setSuccess('Profile deleted successfully'); + setTimeout(() => setSuccess(null), 3000); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to delete profile'); + } + }; + + const handleAddRole = (plugin: RolePlugin) => { + if (!editingProfile) return; + + const schema = plugin.getConfigSchema(); + const defaultConfig = schema.defaultValues || {}; + + const newRole: PluginRoleInstance = { + id: `role-${Date.now()}`, + pluginId: plugin.metadata.id, + name: plugin.metadata.name, + description: plugin.metadata.description, + config: defaultConfig, + executionSettings: { + enabled: true, + timeout: 180, + maxRetries: 1, + maxLoops: plugin.metadata.category === 'reviewer' ? 2 : 1 + } + }; + + setEditingRole(newRole); + setSelectedPlugin(plugin); + setShowRoleModal(true); + }; + + const handleEditRole = (role: PluginRoleInstance) => { + const plugin = availablePlugins.find(p => p.metadata.id === role.pluginId); + if (!plugin) return; + + setEditingRole({ ...role }); + setSelectedPlugin(plugin); + setShowRoleModal(true); + }; + + const handleSaveRole = () => { + if (!editingProfile || !editingRole || !selectedPlugin) return; + + try { + const validation = selectedPlugin.validateConfig(editingRole.config); + if (!validation.isValid) { + setError(`Role configuration errors: ${validation.errors?.join(', ')}`); + return; + } + + const existingIndex = editingProfile.roles.findIndex(r => r.id === editingRole.id); + if (existingIndex >= 0) { + editingProfile.roles[existingIndex] = editingRole; + } else { + editingProfile.roles.push(editingRole); + editingProfile.executionOrder.push(editingRole.id); + } + + setEditingProfile({ ...editingProfile }); + setShowRoleModal(false); + setEditingRole(null); + setSelectedPlugin(null); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to save role'); + } + }; + + const handleRemoveRole = (roleId: string) => { + if (!editingProfile) return; + + setEditingProfile({ + ...editingProfile, + roles: editingProfile.roles.filter(r => r.id !== roleId), + executionOrder: editingProfile.executionOrder.filter(id => id !== roleId) + }); + }; + + const renderConfigField = (field: ConfigField, value: any, onChange: (value: any) => void) => { + switch (field.type) { + case 'string': + return ( + onChange(e.target.value)} + className="w-full px-3 py-2 border border-theme rounded-md bg-theme-elevated text-theme-primary text-sm" + disabled={disabled} + /> + ); + + case 'textarea': + return ( +