parallel optimization groundwork

This commit is contained in:
LPF
2026-05-10 17:27:06 +08:00
parent ce2263ac8c
commit 7b07bb270b
37 changed files with 6896 additions and 3481 deletions

157
pkg/tools/bootstrap.go Normal file
View File

@@ -0,0 +1,157 @@
package tools
import (
"context"
"fmt"
"strings"
"time"
"github.com/YspCoder/clawgo/pkg/bus"
"github.com/YspCoder/clawgo/pkg/config"
)
func BootstrapDefaultTools(ctx context.Context, opts BootstrapOptions) (*BootstrapResult, error) {
if opts.Config == nil {
return nil, fmt.Errorf("config is required")
}
workspace := strings.TrimSpace(opts.Workspace)
if workspace == "" {
workspace = opts.Config.WorkspacePath()
}
if ctx == nil {
ctx = context.Background()
}
registry := NewToolRegistry()
processManager := opts.ProcessManager
if processManager == nil {
processManager = NewProcessManager(workspace)
}
registerFilesystemTools(registry, workspace)
registerShellTools(registry, opts, workspace, processManager)
registerCronTools(registry, opts)
maxParallelCalls, parallelSafe := bootstrapParallelConfig(opts.Config)
registerWebTools(registry, opts, maxParallelCalls, parallelSafe)
registerMCPTools(ctx, registry, opts, workspace)
registerMessageTool(registry, opts.MessageBus)
subagentManager, subagentRouter := registerSubagentTools(registry, opts, workspace)
registerSessionTools(registry, opts)
registerMemoryTools(registry, workspace)
registry.Register(NewParallelTool(registry, maxParallelCalls, parallelSafe))
registry.Register(NewBrowserTool())
registry.Register(NewCameraTool(workspace))
registry.Register(NewSystemInfoTool())
return &BootstrapResult{
Registry: registry,
ProcessManager: processManager,
SubagentManager: subagentManager,
SubagentRouter: subagentRouter,
}, nil
}
func registerFilesystemTools(registry *ToolRegistry, workspace string) {
registry.Register(NewReadFileTool(workspace))
registry.Register(NewWriteFileTool(workspace))
registry.Register(NewListDirTool(workspace))
registry.Register(NewSkillExecTool(workspace))
registry.Register(NewEditFileTool(workspace))
}
func registerShellTools(registry *ToolRegistry, opts BootstrapOptions, workspace string, processManager *ProcessManager) {
registry.Register(NewExecTool(opts.Config.Tools.Shell, workspace, processManager))
registry.Register(NewProcessTool(processManager))
}
func registerCronTools(registry *ToolRegistry, opts BootstrapOptions) {
if opts.CronService == nil {
return
}
registry.Register(NewRemindTool(opts.CronService))
registry.Register(NewCronTool(opts.CronService))
}
func bootstrapParallelConfig(cfg *config.Config) (int, map[string]struct{}) {
maxParallelCalls := cfg.Agents.Defaults.Execution.ToolMaxParallelCalls
if maxParallelCalls <= 0 {
maxParallelCalls = 4
}
parallelSafe := make(map[string]struct{})
for _, name := range cfg.Agents.Defaults.Execution.ToolParallelSafeNames {
trimmed := strings.TrimSpace(name)
if trimmed != "" {
parallelSafe[trimmed] = struct{}{}
}
}
return maxParallelCalls, parallelSafe
}
func registerWebTools(registry *ToolRegistry, opts BootstrapOptions, maxParallelCalls int, parallelSafe map[string]struct{}) {
searchCfg := opts.Config.Tools.Web.Search
registry.Register(NewWebSearchTool(searchCfg.APIKey, searchCfg.MaxResults))
webFetchTool := NewWebFetchTool(50000)
registry.Register(webFetchTool)
registry.Register(NewParallelFetchTool(webFetchTool, maxParallelCalls, parallelSafe))
}
func registerMCPTools(ctx context.Context, registry *ToolRegistry, opts BootstrapOptions, workspace string) {
mcpCfg := opts.Config.Tools.MCP
if !mcpCfg.Enabled {
return
}
mcpTool := NewMCPTool(workspace, mcpCfg)
registry.Register(mcpTool)
timeoutSec := mcpCfg.RequestTimeoutSec
if timeoutSec <= 0 {
timeoutSec = 20
}
discoveryCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSec)*time.Second)
defer cancel()
for _, remoteTool := range mcpTool.DiscoverTools(discoveryCtx) {
registry.Register(remoteTool)
}
}
func registerMessageTool(registry *ToolRegistry, msgBus *bus.MessageBus) {
messageTool := NewMessageTool()
if msgBus != nil {
messageTool.SetSendCallback(func(channel, chatID, action, content, media, messageID, emoji string, buttons [][]bus.Button) error {
msgBus.PublishOutbound(bus.OutboundMessage{
Channel: channel,
ChatID: chatID,
Content: content,
Media: media,
Buttons: buttons,
Action: action,
MessageID: messageID,
Emoji: emoji,
})
return nil
})
}
registry.Register(messageTool)
}
func registerSubagentTools(registry *ToolRegistry, opts BootstrapOptions, workspace string) (*SubagentManager, *SubagentRouter) {
subagentManager := NewSubagentManager(opts.Provider, workspace, opts.MessageBus)
subagentRouter := NewSubagentRouter(subagentManager)
registry.Register(NewSpawnTool(subagentManager))
if store := subagentManager.ProfileStore(); store != nil {
registry.Register(NewSubagentProfileTool(store))
}
return subagentManager, subagentRouter
}
func registerSessionTools(registry *ToolRegistry, opts BootstrapOptions) {
registry.Register(NewSessionsTool(opts.SessionList, opts.SessionHistory))
}
func registerMemoryTools(registry *ToolRegistry, workspace string) {
registry.Register(NewMemorySearchTool(workspace))
registry.Register(NewMemoryGetTool(workspace))
registry.Register(NewMemoryWriteTool(workspace))
}

View File

@@ -0,0 +1,30 @@
package tools
import (
"github.com/YspCoder/clawgo/pkg/bus"
"github.com/YspCoder/clawgo/pkg/config"
"github.com/YspCoder/clawgo/pkg/cron"
"github.com/YspCoder/clawgo/pkg/providers"
)
type SessionListFunc func(limit int) []SessionInfo
type SessionHistoryFunc func(key string, limit int) []providers.Message
type BootstrapOptions struct {
Config *config.Config
Workspace string
MessageBus *bus.MessageBus
CronService *cron.CronService
Provider providers.LLMProvider
ProcessManager *ProcessManager
SessionList SessionListFunc
SessionHistory SessionHistoryFunc
}
type BootstrapResult struct {
Registry *ToolRegistry
ProcessManager *ProcessManager
SubagentManager *SubagentManager
SubagentRouter *SubagentRouter
}

View File

@@ -0,0 +1,61 @@
package tools
import (
"reflect"
"sort"
"testing"
"github.com/YspCoder/clawgo/pkg/config"
)
func TestBootstrapDefaultToolsRegistersExpectedLocalTools(t *testing.T) {
cfg := config.DefaultConfig()
cfg.Agents.Defaults.Workspace = t.TempDir()
cfg.Tools.MCP.Enabled = false
result, err := BootstrapDefaultTools(t.Context(), BootstrapOptions{
Config: cfg,
Workspace: cfg.WorkspacePath(),
})
if err != nil {
t.Fatalf("bootstrap default tools: %v", err)
}
if result == nil || result.Registry == nil {
t.Fatalf("expected registry in bootstrap result")
}
if result.ProcessManager == nil {
t.Fatalf("expected process manager in bootstrap result")
}
if result.SubagentManager == nil || result.SubagentRouter == nil {
t.Fatalf("expected subagent manager and router in bootstrap result")
}
got := result.Registry.List()
sort.Strings(got)
want := []string{
"browser",
"camera_snap",
"edit_file",
"exec",
"list_dir",
"memory_get",
"memory_search",
"memory_write",
"message",
"parallel",
"parallel_fetch",
"process",
"read_file",
"sessions",
"skill_exec",
"spawn",
"subagent_profile",
"system_info",
"web_fetch",
"web_search",
"write_file",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("default tool names mismatch\n got: %v\nwant: %v", got, want)
}
}

View File

@@ -346,13 +346,30 @@ type mcpClient struct {
cmd *exec.Cmd
stdin io.WriteCloser
reader *bufio.Reader
stderr bytes.Buffer
stderr mcpSafeBuffer
writeMu sync.Mutex
waiters sync.Map
nextID atomic.Int64
}
type mcpSafeBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (b *mcpSafeBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.Write(p)
}
func (b *mcpSafeBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.String()
}
type mcpInbound struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`

View File

@@ -15,7 +15,7 @@ func TestRemindTool_UsesToolContextForDeliveryTarget(t *testing.T) {
tool.SetContext("telegram", "chat-123")
_, err := tool.Execute(context.Background(), map[string]interface{}{
"message": "鍠濇按",
"message": "喝水",
"time_expr": "10m",
})
if err != nil {