5510ea8f5a
Add an opt-in human approval gate so Agent runs pause before executing MCP tools that operators flag as dangerous, surface an approval card in the chat UI, and only resume after the user approves (optionally with edited args) or rejects. Backend - New mcp_tool_approvals table + repo/service to mark per-tool approval required (PG migration 000042 + sqlite init). - approval.Gate coordinates RequestAndWait / Resolve with sync.Once delivery, configurable timeout, and Redis Pub/Sub fan-out so multi- replica deployments work without sticky sessions. - MCPTool.Execute integrates the gate; uses a round-level ApprovalCtx (without the per-tool 60s timeout) for the wait, and re-derives a fresh 60s exec ctx after approval so CallTool keeps a full window. - New SSE response types (tool_approval_required / _resolved) and EventBus events plumb approval state to AgentStreamDisplay. - REST: list/set per-tool approval flag, resolve pending approval. - Configurable via agent.tool_approval_timeout_seconds (yaml) or WEKNORA_AGENT_TOOL_APPROVAL_TIMEOUT env (accepts seconds or Go duration). Frontend - MCP settings: per-tool "require approval" switch on the test panel. - Chat: ToolApprovalCard renders the pause point with editable JSON args, validation feedback, mm:ss countdown that turns warning/danger near deadline, and a resolved state that retains context. - i18n strings added for zh-CN / en-US / ko-KR / ru-RU. Docs - docs/zh/mcp-approval.md covering behavior, config, API, deployment considerations (Redis cross-instance, restart limitations).
235 lines
6.7 KiB
Go
235 lines
6.7 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// EventType represents the type of event in the system
|
|
type EventType string
|
|
|
|
const (
|
|
// Query processing events
|
|
EventQueryReceived EventType = "query.received" // 用户查询到达
|
|
EventQueryValidated EventType = "query.validated" // 查询验证完成
|
|
EventQueryPreprocess EventType = "query.preprocess" // 查询预处理
|
|
EventQueryRewrite EventType = "query.rewrite" // 查询改写
|
|
EventQueryRewritten EventType = "query.rewritten" // 查询改写完成
|
|
|
|
// Retrieval events
|
|
EventRetrievalStart EventType = "retrieval.start" // 检索开始
|
|
EventRetrievalVector EventType = "retrieval.vector" // 向量检索
|
|
EventRetrievalKeyword EventType = "retrieval.keyword" // 关键词检索
|
|
EventRetrievalEntity EventType = "retrieval.entity" // 实体检索
|
|
EventRetrievalComplete EventType = "retrieval.complete" // 检索完成
|
|
|
|
// Rerank events
|
|
EventRerankStart EventType = "rerank.start" // 排序开始
|
|
EventRerankComplete EventType = "rerank.complete" // 排序完成
|
|
|
|
// Merge events
|
|
EventMergeStart EventType = "merge.start" // 合并开始
|
|
EventMergeComplete EventType = "merge.complete" // 合并完成
|
|
|
|
// Chat completion events
|
|
EventChatStart EventType = "chat.start" // 聊天生成开始
|
|
EventChatComplete EventType = "chat.complete" // 聊天生成完成
|
|
EventChatStream EventType = "chat.stream" // 聊天流式输出
|
|
|
|
// Agent events
|
|
EventAgentQuery EventType = "agent.query" // Agent 查询开始
|
|
EventAgentPlan EventType = "agent.plan" // Agent 计划生成
|
|
EventAgentStep EventType = "agent.step" // Agent 步骤执行
|
|
EventAgentTool EventType = "agent.tool" // Agent 工具调用
|
|
EventAgentComplete EventType = "agent.complete" // Agent 完成
|
|
|
|
// Agent streaming events (for real-time feedback)
|
|
EventAgentThought EventType = "thought" // Agent 思考过程
|
|
EventAgentToolCall EventType = "tool_call" // 工具调用通知
|
|
EventAgentToolResult EventType = "tool_result" // 工具结果
|
|
EventAgentReflection EventType = "reflection" // Agent 反思
|
|
EventAgentReferences EventType = "references" // 知识引用
|
|
EventAgentFinalAnswer EventType = "final_answer" // 最终答案
|
|
|
|
// MCP tool human approval (issue #1173)
|
|
EventToolApprovalRequired EventType = "tool_approval_required"
|
|
EventToolApprovalResolved EventType = "tool_approval_resolved"
|
|
|
|
// Error events
|
|
EventError EventType = "error" // 错误事件
|
|
|
|
// Session events
|
|
EventSessionTitle EventType = "session_title" // 会话标题更新
|
|
|
|
// Control events
|
|
EventStop EventType = "stop" // 停止对话生成
|
|
)
|
|
|
|
// Event represents an event in the system
|
|
type Event struct {
|
|
ID string // 事件ID (自动生成UUID,用于流式更新追踪)
|
|
Type EventType // 事件类型
|
|
SessionID string // 会话ID
|
|
Data interface{} // 事件数据
|
|
Metadata map[string]interface{} // 事件元数据
|
|
RequestID string // 请求ID
|
|
}
|
|
|
|
// EventHandler is a function that handles events
|
|
type EventHandler func(ctx context.Context, event Event) error
|
|
|
|
// EventBus manages event publishing and subscription
|
|
type EventBus struct {
|
|
mu sync.RWMutex
|
|
handlers map[EventType][]EventHandler
|
|
asyncMode bool // 是否异步处理事件
|
|
}
|
|
|
|
// NewEventBus creates a new EventBus instance
|
|
func NewEventBus() *EventBus {
|
|
return &EventBus{
|
|
handlers: make(map[EventType][]EventHandler),
|
|
asyncMode: false,
|
|
}
|
|
}
|
|
|
|
// NewAsyncEventBus creates a new EventBus with async mode enabled
|
|
func NewAsyncEventBus() *EventBus {
|
|
return &EventBus{
|
|
handlers: make(map[EventType][]EventHandler),
|
|
asyncMode: true,
|
|
}
|
|
}
|
|
|
|
// On registers an event handler for a specific event type
|
|
// Multiple handlers can be registered for the same event type
|
|
func (eb *EventBus) On(eventType EventType, handler EventHandler) {
|
|
eb.mu.Lock()
|
|
defer eb.mu.Unlock()
|
|
|
|
eb.handlers[eventType] = append(eb.handlers[eventType], handler)
|
|
}
|
|
|
|
// Off removes all handlers for a specific event type
|
|
func (eb *EventBus) Off(eventType EventType) {
|
|
eb.mu.Lock()
|
|
defer eb.mu.Unlock()
|
|
|
|
delete(eb.handlers, eventType)
|
|
}
|
|
|
|
// Emit publishes an event to all registered handlers
|
|
// Returns error if any handler fails (in sync mode)
|
|
// Automatically generates an ID for the event if not provided (from source)
|
|
func (eb *EventBus) Emit(ctx context.Context, event Event) error {
|
|
// Auto-generate ID if not provided (from source)
|
|
if event.ID == "" {
|
|
event.ID = uuid.New().String()
|
|
}
|
|
|
|
eb.mu.RLock()
|
|
handlers, exists := eb.handlers[event.Type]
|
|
eb.mu.RUnlock()
|
|
|
|
if !exists || len(handlers) == 0 {
|
|
// No handlers registered for this event type
|
|
return nil
|
|
}
|
|
|
|
if eb.asyncMode {
|
|
// Async mode: fire and forget
|
|
for _, handler := range handlers {
|
|
h := handler // capture loop variable
|
|
go func() {
|
|
_ = h(ctx, event)
|
|
}()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Sync mode: execute handlers sequentially
|
|
for _, handler := range handlers {
|
|
if err := handler(ctx, event); err != nil {
|
|
return fmt.Errorf("event handler failed for %s: %w", event.Type, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EmitAndWait publishes an event and waits for all handlers to complete
|
|
// This method works in both sync and async mode
|
|
// Automatically generates an ID for the event if not provided (from source)
|
|
func (eb *EventBus) EmitAndWait(ctx context.Context, event Event) error {
|
|
// Auto-generate ID if not provided (from source)
|
|
if event.ID == "" {
|
|
event.ID = uuid.New().String()
|
|
}
|
|
|
|
eb.mu.RLock()
|
|
handlers, exists := eb.handlers[event.Type]
|
|
eb.mu.RUnlock()
|
|
|
|
if !exists || len(handlers) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
errChan := make(chan error, len(handlers))
|
|
|
|
for _, handler := range handlers {
|
|
wg.Add(1)
|
|
h := handler // capture loop variable
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := h(ctx, event); err != nil {
|
|
errChan <- err
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errChan)
|
|
|
|
// Collect errors
|
|
for err := range errChan {
|
|
if err != nil {
|
|
return fmt.Errorf("event handler failed for %s: %w", event.Type, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasHandlers checks if there are any handlers registered for an event type
|
|
func (eb *EventBus) HasHandlers(eventType EventType) bool {
|
|
eb.mu.RLock()
|
|
defer eb.mu.RUnlock()
|
|
|
|
handlers, exists := eb.handlers[eventType]
|
|
return exists && len(handlers) > 0
|
|
}
|
|
|
|
// GetHandlerCount returns the number of handlers for a specific event type
|
|
func (eb *EventBus) GetHandlerCount(eventType EventType) int {
|
|
eb.mu.RLock()
|
|
defer eb.mu.RUnlock()
|
|
|
|
if handlers, exists := eb.handlers[eventType]; exists {
|
|
return len(handlers)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Clear removes all event handlers
|
|
func (eb *EventBus) Clear() {
|
|
eb.mu.Lock()
|
|
defer eb.mu.Unlock()
|
|
|
|
eb.handlers = make(map[EventType][]EventHandler)
|
|
}
|