feat: RAG, memory, plugins, lessons, file editor, split panes, Markdown rendering, PWA + UI overhaul
All checks were successful
Beta Release / beta (push) Successful in 5m9s
All checks were successful
Beta Release / beta (push) Successful in 5m9s
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>
This commit is contained in:
224
internal/plugins/plugin.go
Normal file
224
internal/plugins/plugin.go
Normal file
@@ -0,0 +1,224 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user