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>
95 lines
2.1 KiB
Go
95 lines
2.1 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
|
|
"github.com/muyue/muyue/internal/agent"
|
|
)
|
|
|
|
type HookType string
|
|
|
|
const (
|
|
BeforeToolCall HookType = "before_tool_call"
|
|
AfterToolCall HookType = "after_tool_call"
|
|
OnConversationStart HookType = "on_conversation_start"
|
|
OnToolError HookType = "on_tool_error"
|
|
)
|
|
|
|
type HookFunc func(ctx context.Context, payload HookPayload) error
|
|
|
|
type HookPayload struct {
|
|
ToolName string `json:"tool_name"`
|
|
Arguments json.RawMessage `json:"arguments,omitempty"`
|
|
Response *agent.ToolResponse `json:"response,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type Hook struct {
|
|
Type HookType
|
|
Plugin string
|
|
Priority int
|
|
Fn HookFunc
|
|
}
|
|
|
|
type HookRegistry struct {
|
|
mu sync.RWMutex
|
|
hooks map[HookType][]Hook
|
|
}
|
|
|
|
func NewHookRegistry() *HookRegistry {
|
|
return &HookRegistry{
|
|
hooks: make(map[HookType][]Hook),
|
|
}
|
|
}
|
|
|
|
func (hr *HookRegistry) Register(hookType HookType, pluginName string, priority int, fn HookFunc) {
|
|
hr.mu.Lock()
|
|
defer hr.mu.Unlock()
|
|
|
|
h := Hook{
|
|
Type: hookType,
|
|
Plugin: pluginName,
|
|
Priority: priority,
|
|
Fn: fn,
|
|
}
|
|
hr.hooks[hookType] = append(hr.hooks[hookType], h)
|
|
|
|
for i := len(hr.hooks[hookType]) - 1; i > 0; i-- {
|
|
if hr.hooks[hookType][i].Priority < hr.hooks[hookType][i-1].Priority {
|
|
hr.hooks[hookType][i], hr.hooks[hookType][i-1] = hr.hooks[hookType][i-1], hr.hooks[hookType][i]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (hr *HookRegistry) Fire(ctx context.Context, hookType HookType, payload HookPayload) error {
|
|
hr.mu.RLock()
|
|
hooks := make([]Hook, len(hr.hooks[hookType]))
|
|
copy(hooks, hr.hooks[hookType])
|
|
hr.mu.RUnlock()
|
|
|
|
for _, h := range hooks {
|
|
if err := h.Fn(ctx, payload); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (hr *HookRegistry) RemoveByPlugin(pluginName string) {
|
|
hr.mu.Lock()
|
|
defer hr.mu.Unlock()
|
|
|
|
for hookType := range hr.hooks {
|
|
filtered := make([]Hook, 0, len(hr.hooks[hookType]))
|
|
for _, h := range hr.hooks[hookType] {
|
|
if h.Plugin != pluginName {
|
|
filtered = append(filtered, h)
|
|
}
|
|
}
|
|
hr.hooks[hookType] = filtered
|
|
}
|
|
}
|