All checks were successful
Stable Release / stable (push) Successful in 1m34s
Major additions: - RAG pipeline (indexing, chunking, search) with sidebar upload button - Memory system with CRUD API - Plugins and lessons modules - MCP discovery and MCP server - Advanced skills (auto-create, conditional, improver) - Agent browser/image support, delegate, sessions - File editor with CodeMirror in split panes - Markdown rendering via react-markdown + KaTeX + highlight.js - Raw markdown toggle - PWA manifest + service worker - Extension UI redesign with new design tokens and studio-style chat - Pipeline API for chat streaming - Mobile responsive layout 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
225 lines
4.9 KiB
Go
225 lines
4.9 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/muyue/muyue/internal/agent"
|
|
)
|
|
|
|
type PluginStatus string
|
|
|
|
const (
|
|
StatusEnabled PluginStatus = "enabled"
|
|
StatusDisabled PluginStatus = "disabled"
|
|
StatusError PluginStatus = "error"
|
|
)
|
|
|
|
type Plugin struct {
|
|
name string
|
|
version string
|
|
description string
|
|
status PluginStatus
|
|
tools []*agent.ToolDefinition
|
|
hooks map[HookType]HookFunc
|
|
init func(ctx context.Context, registry *agent.Registry) error
|
|
}
|
|
|
|
func NewPlugin(name, version, description string) *Plugin {
|
|
return &Plugin{
|
|
name: name,
|
|
version: version,
|
|
description: description,
|
|
status: StatusDisabled,
|
|
tools: make([]*agent.ToolDefinition, 0),
|
|
hooks: make(map[HookType]HookFunc),
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) Name() string { return p.name }
|
|
func (p *Plugin) Version() string { return p.version }
|
|
func (p *Plugin) Description() string { return p.description }
|
|
func (p *Plugin) Status() PluginStatus { return p.status }
|
|
|
|
func (p *Plugin) AddTool(tool *ToolDefinition) *Plugin {
|
|
td := &agent.ToolDefinition{
|
|
Name: tool.Name,
|
|
Description: tool.Description,
|
|
Params: tool.Params,
|
|
Handler: tool.Handler,
|
|
}
|
|
p.tools = append(p.tools, td)
|
|
return p
|
|
}
|
|
|
|
func (p *Plugin) AddToolGeneric(params interface{}, name, description string, handler func(ctx context.Context, raw json.RawMessage) (agent.ToolResponse, error)) *Plugin {
|
|
paramsSchema, err := generatePluginSchema(params)
|
|
if err == nil {
|
|
td := &agent.ToolDefinition{
|
|
Name: name,
|
|
Description: description,
|
|
Params: paramsSchema,
|
|
Handler: handler,
|
|
}
|
|
p.tools = append(p.tools, td)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (p *Plugin) AddHook(hookType HookType, fn HookFunc) *Plugin {
|
|
p.hooks[hookType] = fn
|
|
return p
|
|
}
|
|
|
|
func (p *Plugin) SetInit(fn func(ctx context.Context, registry *agent.Registry) error) *Plugin {
|
|
p.init = fn
|
|
return p
|
|
}
|
|
|
|
type ToolDefinition struct {
|
|
Name string
|
|
Description string
|
|
Params json.RawMessage
|
|
Handler func(ctx context.Context, args json.RawMessage) (agent.ToolResponse, error)
|
|
}
|
|
|
|
type PluginInfo struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Description string `json:"description"`
|
|
Status PluginStatus `json:"status"`
|
|
ToolCount int `json:"tool_count"`
|
|
HookTypes []string `json:"hook_types,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type Manager struct {
|
|
mu sync.RWMutex
|
|
plugins map[string]*Plugin
|
|
hooks *HookRegistry
|
|
enabled map[string]bool
|
|
}
|
|
|
|
func NewManager(hooks *HookRegistry) *Manager {
|
|
return &Manager{
|
|
plugins: make(map[string]*Plugin),
|
|
hooks: hooks,
|
|
enabled: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
func (m *Manager) Register(p *Plugin) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, exists := m.plugins[p.name]; exists {
|
|
return fmt.Errorf("plugin %q already registered", p.name)
|
|
}
|
|
|
|
m.plugins[p.name] = p
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Enable(ctx context.Context, name string, registry *agent.Registry) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
p, ok := m.plugins[name]
|
|
if !ok {
|
|
return fmt.Errorf("plugin %q not found", name)
|
|
}
|
|
|
|
if p.status == StatusEnabled {
|
|
return nil
|
|
}
|
|
|
|
if p.init != nil {
|
|
if err := p.init(ctx, registry); err != nil {
|
|
p.status = StatusError
|
|
return fmt.Errorf("plugin %q init failed: %w", name, err)
|
|
}
|
|
}
|
|
|
|
for _, tool := range p.tools {
|
|
if err := registry.Register(tool); err != nil {
|
|
p.status = StatusError
|
|
return fmt.Errorf("plugin %q register tool %q: %w", name, tool.Name, err)
|
|
}
|
|
}
|
|
|
|
for hookType, fn := range p.hooks {
|
|
m.hooks.Register(hookType, name, 10, fn)
|
|
}
|
|
|
|
p.status = StatusEnabled
|
|
m.enabled[name] = true
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Disable(name string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
p, ok := m.plugins[name]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if p.status != StatusEnabled {
|
|
return
|
|
}
|
|
|
|
m.hooks.RemoveByPlugin(name)
|
|
p.status = StatusDisabled
|
|
delete(m.enabled, name)
|
|
}
|
|
|
|
func (m *Manager) Get(name string) (*Plugin, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
p, ok := m.plugins[name]
|
|
return p, ok
|
|
}
|
|
|
|
func (m *Manager) List() []PluginInfo {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
result := make([]PluginInfo, 0, len(m.plugins))
|
|
for _, p := range m.plugins {
|
|
info := PluginInfo{
|
|
Name: p.name,
|
|
Version: p.version,
|
|
Description: p.description,
|
|
Status: p.status,
|
|
ToolCount: len(p.tools),
|
|
}
|
|
for ht := range p.hooks {
|
|
info.HookTypes = append(info.HookTypes, string(ht))
|
|
}
|
|
result = append(result, info)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *Manager) EnabledNames() []string {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
names := make([]string, 0, len(m.enabled))
|
|
for name := range m.enabled {
|
|
names = append(names, name)
|
|
}
|
|
return names
|
|
}
|
|
|
|
func (m *Manager) EnableFromConfig(ctx context.Context, enabledList []string, registry *agent.Registry) {
|
|
for _, name := range enabledList {
|
|
if err := m.Enable(ctx, name, registry); err != nil {
|
|
_ = err
|
|
}
|
|
}
|
|
}
|