Resolve data races in channels, logger, spawn, and subagent
This commit is contained in:
parent
8d757fbb6f
commit
c176d1faab
5 changed files with 41 additions and 19 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
)
|
)
|
||||||
|
|
@ -20,7 +21,7 @@ type Channel interface {
|
||||||
type BaseChannel struct {
|
type BaseChannel struct {
|
||||||
config interface{}
|
config interface{}
|
||||||
bus *bus.MessageBus
|
bus *bus.MessageBus
|
||||||
running bool
|
running atomic.Bool
|
||||||
name string
|
name string
|
||||||
allowList []string
|
allowList []string
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +32,6 @@ func NewBaseChannel(name string, config interface{}, bus *bus.MessageBus, allowL
|
||||||
bus: bus,
|
bus: bus,
|
||||||
name: name,
|
name: name,
|
||||||
allowList: allowList,
|
allowList: allowList,
|
||||||
running: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ func (c *BaseChannel) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BaseChannel) IsRunning() bool {
|
func (c *BaseChannel) IsRunning() bool {
|
||||||
return c.running
|
return c.running.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BaseChannel) IsAllowed(senderID string) bool {
|
func (c *BaseChannel) IsAllowed(senderID string) bool {
|
||||||
|
|
@ -104,5 +104,5 @@ func (c *BaseChannel) HandleMessage(senderID, chatID, content string, media []st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BaseChannel) setRunning(running bool) {
|
func (c *BaseChannel) setRunning(running bool) {
|
||||||
c.running = running
|
c.running.Store(running)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ type TelegramChannel struct {
|
||||||
*BaseChannel
|
*BaseChannel
|
||||||
bot *telego.Bot
|
bot *telego.Bot
|
||||||
commands TelegramCommander
|
commands TelegramCommander
|
||||||
config *config.Config
|
config config.TelegramConfig
|
||||||
chatIDs map[string]int64
|
chatIDs sync.Map
|
||||||
transcriber *voice.GroqTranscriber
|
transcriber *voice.GroqTranscriber
|
||||||
placeholders sync.Map // chatID -> messageID
|
placeholders sync.Map // chatID -> messageID
|
||||||
stopThinking sync.Map // chatID -> thinkingCancel
|
stopThinking sync.Map // chatID -> thinkingCancel
|
||||||
|
|
@ -72,8 +72,8 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
|
||||||
BaseChannel: base,
|
BaseChannel: base,
|
||||||
commands: NewTelegramCommands(bot, cfg),
|
commands: NewTelegramCommands(bot, cfg),
|
||||||
bot: bot,
|
bot: bot,
|
||||||
config: cfg,
|
config: telegramCfg,
|
||||||
chatIDs: make(map[string]int64),
|
chatIDs: sync.Map{},
|
||||||
transcriber: nil,
|
transcriber: nil,
|
||||||
placeholders: sync.Map{},
|
placeholders: sync.Map{},
|
||||||
stopThinking: sync.Map{},
|
stopThinking: sync.Map{},
|
||||||
|
|
@ -210,7 +210,7 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
chatID := message.Chat.ID
|
chatID := message.Chat.ID
|
||||||
c.chatIDs[senderID] = chatID
|
c.chatIDs.Store(senderID, chatID)
|
||||||
|
|
||||||
content := ""
|
content := ""
|
||||||
mediaPaths := []string{}
|
mediaPaths := []string{}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,12 @@ func DisableFileLogging() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func logMessage(level LogLevel, component string, message string, fields map[string]interface{}) {
|
func logMessage(level LogLevel, component string, message string, fields map[string]interface{}) {
|
||||||
if level < currentLevel {
|
mu.RLock()
|
||||||
|
lvl := currentLevel
|
||||||
|
file := logger.file
|
||||||
|
mu.RUnlock()
|
||||||
|
|
||||||
|
if level < lvl {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,17 +114,17 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
}
|
}
|
||||||
|
|
||||||
if pc, file, line, ok := runtime.Caller(2); ok {
|
if pc, f, line, ok := runtime.Caller(2); ok {
|
||||||
fn := runtime.FuncForPC(pc)
|
fn := runtime.FuncForPC(pc)
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
entry.Caller = fmt.Sprintf("%s:%d (%s)", file, line, fn.Name())
|
entry.Caller = fmt.Sprintf("%s:%d (%s)", f, line, fn.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if logger.file != nil {
|
if file != nil {
|
||||||
jsonData, err := json.Marshal(entry)
|
jsonData, err := json.Marshal(entry)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.file.WriteString(string(jsonData) + "\n")
|
file.WriteString(string(jsonData) + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ package tools
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SpawnTool struct {
|
type SpawnTool struct {
|
||||||
|
mu sync.Mutex
|
||||||
manager *SubagentManager
|
manager *SubagentManager
|
||||||
originChannel string
|
originChannel string
|
||||||
originChatID string
|
originChatID string
|
||||||
|
|
@ -22,7 +24,9 @@ func NewSpawnTool(manager *SubagentManager) *SpawnTool {
|
||||||
|
|
||||||
// SetCallback implements AsyncTool interface for async completion notification
|
// SetCallback implements AsyncTool interface for async completion notification
|
||||||
func (t *SpawnTool) SetCallback(cb AsyncCallback) {
|
func (t *SpawnTool) SetCallback(cb AsyncCallback) {
|
||||||
|
t.mu.Lock()
|
||||||
t.callback = cb
|
t.callback = cb
|
||||||
|
t.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SpawnTool) Name() string {
|
func (t *SpawnTool) Name() string {
|
||||||
|
|
@ -51,8 +55,10 @@ func (t *SpawnTool) Parameters() map[string]interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SpawnTool) SetContext(channel, chatID string) {
|
func (t *SpawnTool) SetContext(channel, chatID string) {
|
||||||
|
t.mu.Lock()
|
||||||
t.originChannel = channel
|
t.originChannel = channel
|
||||||
t.originChatID = chatID
|
t.originChatID = chatID
|
||||||
|
t.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
|
func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
|
||||||
|
|
@ -67,8 +73,14 @@ func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *T
|
||||||
return ErrorResult("Subagent manager not configured")
|
return ErrorResult("Subagent manager not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.mu.Lock()
|
||||||
|
originChannel := t.originChannel
|
||||||
|
originChatID := t.originChatID
|
||||||
|
callback := t.callback
|
||||||
|
t.mu.Unlock()
|
||||||
|
|
||||||
// Pass callback to manager for async completion notification
|
// Pass callback to manager for async completion notification
|
||||||
result, err := t.manager.Spawn(ctx, task, label, t.originChannel, t.originChatID, t.callback)
|
result, err := t.manager.Spawn(ctx, task, label, originChannel, originChatID, callback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("failed to spawn subagent: %v", err))
|
return ErrorResult(fmt.Sprintf("failed to spawn subagent: %v", err))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,6 @@ func (sm *SubagentManager) Spawn(ctx context.Context, task, label, originChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, callback AsyncCallback) {
|
func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, callback AsyncCallback) {
|
||||||
task.Status = "running"
|
|
||||||
task.Created = time.Now().UnixMilli()
|
|
||||||
|
|
||||||
// Build system prompt for subagent
|
// Build system prompt for subagent
|
||||||
systemPrompt := `You are a subagent. Complete the given task independently and report the result.
|
systemPrompt := `You are a subagent. Complete the given task independently and report the result.
|
||||||
You have access to tools - use them as needed to complete your task.
|
You have access to tools - use them as needed to complete your task.
|
||||||
|
|
@ -209,6 +206,7 @@ func (sm *SubagentManager) ListTasks() []*SubagentTask {
|
||||||
// Unlike SpawnTool which runs tasks asynchronously, SubagentTool waits for completion
|
// Unlike SpawnTool which runs tasks asynchronously, SubagentTool waits for completion
|
||||||
// and returns the result directly in the ToolResult.
|
// and returns the result directly in the ToolResult.
|
||||||
type SubagentTool struct {
|
type SubagentTool struct {
|
||||||
|
mu sync.Mutex
|
||||||
manager *SubagentManager
|
manager *SubagentManager
|
||||||
originChannel string
|
originChannel string
|
||||||
originChatID string
|
originChatID string
|
||||||
|
|
@ -248,11 +246,18 @@ func (t *SubagentTool) Parameters() map[string]interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SubagentTool) SetContext(channel, chatID string) {
|
func (t *SubagentTool) SetContext(channel, chatID string) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
t.originChannel = channel
|
t.originChannel = channel
|
||||||
t.originChatID = chatID
|
t.originChatID = chatID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SubagentTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
|
func (t *SubagentTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult {
|
||||||
|
t.mu.Lock()
|
||||||
|
originChannel := t.originChannel
|
||||||
|
originChatID := t.originChatID
|
||||||
|
t.mu.Unlock()
|
||||||
|
|
||||||
task, ok := args["task"].(string)
|
task, ok := args["task"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrorResult("task is required").WithError(fmt.Errorf("task parameter is required"))
|
return ErrorResult("task is required").WithError(fmt.Errorf("task parameter is required"))
|
||||||
|
|
@ -292,7 +297,7 @@ func (t *SubagentTool) Execute(ctx context.Context, args map[string]interface{})
|
||||||
"max_tokens": 4096,
|
"max_tokens": 4096,
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
},
|
},
|
||||||
}, messages, t.originChannel, t.originChatID)
|
}, messages, originChannel, originChatID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("Subagent execution failed: %v", err)).WithError(err)
|
return ErrorResult(fmt.Sprintf("Subagent execution failed: %v", err)).WithError(err)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue