71f0a1abdb
- 所有Go模块路径从 github.com/yourname/cyrene-ai 迁移到 git.yeij.top/AskaEth/Cyrene - 5个Go Dockerfile添加 GOPROXY=https://goproxy.cn,direct 解决国内构建问题 - ai-core go.mod 添加 pkg/plugins replace 指令 - Caddyfile 简化为 http:// 通配 + handle 保留 /api 前缀 - ethend Dockerfile 适配 (npm install + 仅 COPY package.json) - ethend 新增 RUNNING_IN_DOCKER 环境变量,健康检查改用Docker服务名 - ethend 数据库状态检查支持Docker hostname (postgres/redis/qdrant/minio) - process-manager 新增 CONTAINER_SVC_MAP + Docker模式自动检测 - 统一 docker-compose.dev.db.yml 卷名 (pg_data/redis_data/qdrant_data/minio_data) - docker-compose.yml ethend服务挂载docker.sock + 端口变量化 - 清理 .env 统一后的残留文件与提示信息 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
227 lines
5.3 KiB
Go
227 lines
5.3 KiB
Go
package manager
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene/pkg/plugins/sdk"
|
|
)
|
|
|
|
// PluginManager manages the lifecycle of all plugins and their tools.
|
|
type PluginManager struct {
|
|
mu sync.RWMutex
|
|
plugins map[string]*pluginEntry
|
|
registry *ToolRegistry
|
|
host sdk.HostAPI
|
|
}
|
|
|
|
type pluginEntry struct {
|
|
instance sdk.Plugin
|
|
info sdk.PluginInfo
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func NewPluginManager(registry *ToolRegistry, host sdk.HostAPI) *PluginManager {
|
|
return &PluginManager{
|
|
plugins: make(map[string]*pluginEntry),
|
|
registry: registry,
|
|
host: host,
|
|
}
|
|
}
|
|
|
|
// Install registers a plugin instance.
|
|
func (m *PluginManager) Install(plugin sdk.Plugin) error {
|
|
meta := plugin.Metadata()
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, exists := m.plugins[meta.Name]; exists {
|
|
return fmt.Errorf("plugin %q is already installed", meta.Name)
|
|
}
|
|
|
|
m.plugins[meta.Name] = &pluginEntry{
|
|
instance: plugin,
|
|
info: sdk.PluginInfo{
|
|
Metadata: meta,
|
|
Status: sdk.StatusInstalled,
|
|
InstalledAt: time.Now(),
|
|
Enabled: false,
|
|
},
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Enable activates a plugin: Init → register tools → Start.
|
|
func (m *PluginManager) Enable(ctx context.Context, pluginName string) error {
|
|
m.mu.Lock()
|
|
entry, ok := m.plugins[pluginName]
|
|
m.mu.Unlock()
|
|
if !ok {
|
|
return fmt.Errorf("plugin %q not found", pluginName)
|
|
}
|
|
|
|
m.mu.Lock()
|
|
entry.info.Status = sdk.StatusLoaded
|
|
m.mu.Unlock()
|
|
|
|
meta := entry.instance.Metadata()
|
|
if err := entry.instance.Init(ctx, nil); err != nil {
|
|
m.mu.Lock()
|
|
entry.info.Status = sdk.StatusError
|
|
m.mu.Unlock()
|
|
return fmt.Errorf("plugin %q init failed: %w", meta.Name, err)
|
|
}
|
|
|
|
pluginCtx, cancel := context.WithCancel(context.Background())
|
|
if err := entry.instance.Start(pluginCtx, m.host); err != nil {
|
|
cancel()
|
|
m.mu.Lock()
|
|
entry.info.Status = sdk.StatusError
|
|
m.mu.Unlock()
|
|
return fmt.Errorf("plugin %q start failed: %w", meta.Name, err)
|
|
}
|
|
|
|
tools := entry.instance.Tools()
|
|
toolIDs := make([]string, 0, len(tools))
|
|
for _, t := range tools {
|
|
if err := m.registry.Register(t); err != nil {
|
|
m.registry.UnregisterAll(toolIDs)
|
|
cancel()
|
|
m.mu.Lock()
|
|
entry.info.Status = sdk.StatusError
|
|
m.mu.Unlock()
|
|
return fmt.Errorf("plugin %q tool register failed: %w", meta.Name, err)
|
|
}
|
|
toolIDs = append(toolIDs, t.Definition().ID)
|
|
}
|
|
|
|
m.mu.Lock()
|
|
entry.cancel = cancel
|
|
entry.info.Status = sdk.StatusRunning
|
|
entry.info.Enabled = true
|
|
entry.info.Tools = toolIDs
|
|
m.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Disable stops a plugin and unregisters its tools.
|
|
func (m *PluginManager) Disable(ctx context.Context, pluginName string) error {
|
|
m.mu.Lock()
|
|
entry, ok := m.plugins[pluginName]
|
|
m.mu.Unlock()
|
|
if !ok {
|
|
return fmt.Errorf("plugin %q not found", pluginName)
|
|
}
|
|
|
|
if err := entry.instance.Stop(ctx); err != nil {
|
|
return fmt.Errorf("plugin %q stop failed: %w", pluginName, err)
|
|
}
|
|
if entry.cancel != nil {
|
|
entry.cancel()
|
|
}
|
|
|
|
m.registry.UnregisterAll(entry.info.Tools)
|
|
|
|
m.mu.Lock()
|
|
entry.info.Status = sdk.StatusDisabled
|
|
entry.info.Enabled = false
|
|
entry.info.Tools = nil
|
|
m.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// List returns info for all installed plugins.
|
|
func (m *PluginManager) List() []sdk.PluginInfo {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
result := make([]sdk.PluginInfo, 0, len(m.plugins))
|
|
for _, entry := range m.plugins {
|
|
result = append(result, entry.info)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Get returns info for a single plugin.
|
|
func (m *PluginManager) Get(pluginName string) (*sdk.PluginInfo, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
entry, ok := m.plugins[pluginName]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
info := entry.info
|
|
return &info, true
|
|
}
|
|
|
|
// EnableAll starts all installed plugins.
|
|
func (m *PluginManager) EnableAll(ctx context.Context) []error {
|
|
m.mu.RLock()
|
|
names := make([]string, 0, len(m.plugins))
|
|
for name := range m.plugins {
|
|
names = append(names, name)
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
var errs []error
|
|
for _, name := range names {
|
|
if err := m.Enable(ctx, name); err != nil {
|
|
errs = append(errs, fmt.Errorf("%s: %w", name, err))
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
// Uninstall removes a plugin completely.
|
|
func (m *PluginManager) Uninstall(ctx context.Context, pluginName string) error {
|
|
m.mu.RLock()
|
|
entry, ok := m.plugins[pluginName]
|
|
m.mu.RUnlock()
|
|
if !ok {
|
|
return fmt.Errorf("plugin %q not found", pluginName)
|
|
}
|
|
if entry.info.Status == sdk.StatusRunning {
|
|
if err := m.Disable(ctx, pluginName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.plugins, pluginName)
|
|
return nil
|
|
}
|
|
|
|
// Reload stops and re-starts a plugin.
|
|
func (m *PluginManager) Reload(ctx context.Context, pluginName string) error {
|
|
if err := m.Disable(ctx, pluginName); err != nil {
|
|
return fmt.Errorf("reload disable: %w", err)
|
|
}
|
|
return m.Enable(ctx, pluginName)
|
|
}
|
|
|
|
// Shutdown stops all running plugins gracefully.
|
|
func (m *PluginManager) Shutdown(ctx context.Context) []error {
|
|
m.mu.RLock()
|
|
names := make([]string, 0, len(m.plugins))
|
|
for name, entry := range m.plugins {
|
|
if entry.info.Status == sdk.StatusRunning {
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
var errs []error
|
|
for _, name := range names {
|
|
if err := m.Disable(ctx, name); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
// Registry returns the aggregated tool registry.
|
|
func (m *PluginManager) Registry() *ToolRegistry {
|
|
return m.registry
|
|
}
|