WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/logger"
|
||||
)
|
||||
|
||||
// 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"
|
||||
|
||||
// MCP OAuth in-conversation authorization prompt: emitted when an
|
||||
// OAuth-enabled MCP service is invoked but the current user has not
|
||||
// authorized it yet. The agent pauses until the user authorizes (or the
|
||||
// wait times out / is canceled).
|
||||
EventMCPOAuthRequired EventType = "mcp_oauth_required"
|
||||
EventMCPOAuthResolved EventType = "mcp_oauth_resolved"
|
||||
|
||||
// Error events
|
||||
EventError EventType = "error" // 错误事件
|
||||
|
||||
// Long-term memory recalled for this turn. Emitted once, before the answer
|
||||
// streams, so the UI can show which memories the answer saw.
|
||||
EventMemoryRecalled EventType = "memory_recalled"
|
||||
|
||||
// EventContextCompacted is emitted when older conversation was summarized
|
||||
// away to fit the context window.
|
||||
EventContextCompacted EventType = "context_compacted"
|
||||
|
||||
// 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() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Errorf(ctx, "event handler panic recovered (type=%s): %v", event.Type, r)
|
||||
}
|
||||
}()
|
||||
_ = 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()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errChan <- fmt.Errorf("event handler panic (type=%s): %v", event.Type, r)
|
||||
}
|
||||
}()
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user