* Add tools enable or diable config

This commit is contained in:
lxowalle 2026-03-04 16:39:09 +08:00
parent b82bb9acc0
commit ad8bb3d9b7
5 changed files with 351 additions and 90 deletions

View file

@ -223,19 +223,24 @@ func setupCronTool(
// Create cron service // Create cron service
cronService := cron.NewCronService(cronStorePath, nil) cronService := cron.NewCronService(cronStorePath, nil)
// Create and register CronTool // Create and register CronTool if enabled
cronTool, err := tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg) var cronTool *tools.CronTool
if err != nil { if cfg.Tools.IsToolEnabled("cron") {
log.Fatalf("Critical error during CronTool initialization: %v", err) cronTool, err := tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
if err != nil {
log.Fatalf("Critical error during CronTool initialization: %v", err)
}
agentLoop.RegisterTool(cronTool)
} }
agentLoop.RegisterTool(cronTool) // Set onJob handler
if cronTool != nil {
// Set the onJob handler cronService.SetOnJob(func(job *cron.CronJob) (string, error) {
cronService.SetOnJob(func(job *cron.CronJob) (string, error) { result := cronTool.ExecuteJob(context.Background(), job)
result := cronTool.ExecuteJob(context.Background(), job) return result, nil
return result, nil })
}) }
return cronService return cronService
} }

View file

@ -59,17 +59,30 @@ func NewAgentInstance(
allowWritePaths := compilePatterns(cfg.Tools.AllowWritePaths) allowWritePaths := compilePatterns(cfg.Tools.AllowWritePaths)
toolsRegistry := tools.NewToolRegistry() toolsRegistry := tools.NewToolRegistry()
toolsRegistry.Register(tools.NewReadFileTool(workspace, readRestrict, allowReadPaths))
toolsRegistry.Register(tools.NewWriteFileTool(workspace, restrict, allowWritePaths))
toolsRegistry.Register(tools.NewListDirTool(workspace, readRestrict, allowReadPaths))
execTool, err := tools.NewExecToolWithConfig(workspace, restrict, cfg)
if err != nil {
log.Fatalf("Critical error: unable to initialize exec tool: %v", err)
}
toolsRegistry.Register(execTool)
toolsRegistry.Register(tools.NewEditFileTool(workspace, restrict, allowWritePaths)) if cfg.Tools.IsToolEnabled("read_file") {
toolsRegistry.Register(tools.NewAppendFileTool(workspace, restrict, allowWritePaths)) toolsRegistry.Register(tools.NewReadFileTool(workspace, readRestrict, allowReadPaths))
}
if cfg.Tools.IsToolEnabled("write_file") {
toolsRegistry.Register(tools.NewWriteFileTool(workspace, restrict, allowWritePaths))
}
if cfg.Tools.IsToolEnabled("list_dir") {
toolsRegistry.Register(tools.NewListDirTool(workspace, readRestrict, allowReadPaths))
}
if cfg.Tools.IsToolEnabled("exec") {
execTool, err := tools.NewExecToolWithConfig(workspace, restrict, cfg)
if err != nil {
log.Fatalf("Critical error: unable to initialize exec tool: %v", err)
}
toolsRegistry.Register(execTool)
}
if cfg.Tools.IsToolEnabled("edit_file") {
toolsRegistry.Register(tools.NewEditFileTool(workspace, restrict, allowWritePaths))
}
if cfg.Tools.IsToolEnabled("append_file") {
toolsRegistry.Register(tools.NewAppendFileTool(workspace, restrict, allowWritePaths))
}
sessionsDir := filepath.Join(workspace, "sessions") sessionsDir := filepath.Join(workspace, "sessions")
sessionsManager := session.NewSessionManager(sessionsDir) sessionsManager := session.NewSessionManager(sessionsDir)

View file

@ -105,54 +105,64 @@ func registerSharedTools(
} }
// Web tools // Web tools
searchTool, err := tools.NewWebSearchTool(tools.WebSearchToolOptions{ if cfg.Tools.IsToolEnabled("web_search") {
BraveAPIKey: cfg.Tools.Web.Brave.APIKey, searchTool, err := tools.NewWebSearchTool(tools.WebSearchToolOptions{
BraveMaxResults: cfg.Tools.Web.Brave.MaxResults, BraveAPIKey: cfg.Tools.Web.Brave.APIKey,
BraveEnabled: cfg.Tools.Web.Brave.Enabled, BraveMaxResults: cfg.Tools.Web.Brave.MaxResults,
TavilyAPIKey: cfg.Tools.Web.Tavily.APIKey, BraveEnabled: cfg.Tools.Web.Brave.Enabled,
TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL, TavilyAPIKey: cfg.Tools.Web.Tavily.APIKey,
TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults, TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL,
TavilyEnabled: cfg.Tools.Web.Tavily.Enabled, TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults,
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults, TavilyEnabled: cfg.Tools.Web.Tavily.Enabled,
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled, DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
PerplexityAPIKey: cfg.Tools.Web.Perplexity.APIKey, DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults, PerplexityAPIKey: cfg.Tools.Web.Perplexity.APIKey,
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled, PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
GLMSearchAPIKey: cfg.Tools.Web.GLMSearch.APIKey, PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
GLMSearchBaseURL: cfg.Tools.Web.GLMSearch.BaseURL, GLMSearchAPIKey: cfg.Tools.Web.GLMSearch.APIKey,
GLMSearchEngine: cfg.Tools.Web.GLMSearch.SearchEngine, GLMSearchBaseURL: cfg.Tools.Web.GLMSearch.BaseURL,
GLMSearchMaxResults: cfg.Tools.Web.GLMSearch.MaxResults, GLMSearchEngine: cfg.Tools.Web.GLMSearch.SearchEngine,
GLMSearchEnabled: cfg.Tools.Web.GLMSearch.Enabled, GLMSearchMaxResults: cfg.Tools.Web.GLMSearch.MaxResults,
Proxy: cfg.Tools.Web.Proxy, GLMSearchEnabled: cfg.Tools.Web.GLMSearch.Enabled,
}) Proxy: cfg.Tools.Web.Proxy,
if err != nil { })
logger.ErrorCF("agent", "Failed to create web search tool", map[string]any{"error": err.Error()}) if err != nil {
} else if searchTool != nil { logger.ErrorCF("agent", "Failed to create web search tool", map[string]any{"error": err.Error()})
agent.Tools.Register(searchTool) } else if searchTool != nil {
agent.Tools.Register(searchTool)
}
} }
fetchTool, err := tools.NewWebFetchToolWithProxy(50000, cfg.Tools.Web.Proxy, cfg.Tools.Web.FetchLimitBytes) if cfg.Tools.IsToolEnabled("web_fetch") {
if err != nil { fetchTool, err := tools.NewWebFetchToolWithProxy(50000, cfg.Tools.Web.Proxy, cfg.Tools.Web.FetchLimitBytes)
logger.ErrorCF("agent", "Failed to create web fetch tool", map[string]any{"error": err.Error()}) if err != nil {
} else { logger.ErrorCF("agent", "Failed to create web fetch tool", map[string]any{"error": err.Error()})
agent.Tools.Register(fetchTool) } else {
agent.Tools.Register(fetchTool)
}
} }
// Hardware tools (I2C, SPI) - Linux only, returns error on other platforms // Hardware tools (I2C, SPI) - Linux only, returns error on other platforms
agent.Tools.Register(tools.NewI2CTool()) if cfg.Tools.IsToolEnabled("i2c") {
agent.Tools.Register(tools.NewSPITool()) agent.Tools.Register(tools.NewI2CTool())
}
if cfg.Tools.IsToolEnabled("spi") {
agent.Tools.Register(tools.NewSPITool())
}
// Message tool // Message tool
messageTool := tools.NewMessageTool() if cfg.Tools.IsToolEnabled("message") {
messageTool.SetSendCallback(func(channel, chatID, content string) error { messageTool := tools.NewMessageTool()
pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) messageTool.SetSendCallback(func(channel, chatID, content string) error {
defer pubCancel() pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second)
return msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ defer pubCancel()
Channel: channel, return msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{
ChatID: chatID, Channel: channel,
Content: content, ChatID: chatID,
Content: content,
})
}) })
}) agent.Tools.Register(messageTool)
agent.Tools.Register(messageTool) }
// Skill discovery and installation tools // Skill discovery and installation tools
registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{ registryMgr := skills.NewRegistryManagerFromConfig(skills.RegistryConfig{
@ -163,18 +173,24 @@ func registerSharedTools(
cfg.Tools.Skills.SearchCache.MaxSize, cfg.Tools.Skills.SearchCache.MaxSize,
time.Duration(cfg.Tools.Skills.SearchCache.TTLSeconds)*time.Second, time.Duration(cfg.Tools.Skills.SearchCache.TTLSeconds)*time.Second,
) )
agent.Tools.Register(tools.NewFindSkillsTool(registryMgr, searchCache)) if cfg.Tools.IsToolEnabled("find_skills") {
agent.Tools.Register(tools.NewInstallSkillTool(registryMgr, agent.Workspace)) agent.Tools.Register(tools.NewFindSkillsTool(registryMgr, searchCache))
}
if cfg.Tools.IsToolEnabled("install_skill") {
agent.Tools.Register(tools.NewInstallSkillTool(registryMgr, agent.Workspace))
}
// Spawn tool with allowlist checker // Spawn tool with allowlist checker
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus) if cfg.Tools.IsToolEnabled("spawn") {
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature) subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace, msgBus)
spawnTool := tools.NewSpawnTool(subagentManager) subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
currentAgentID := agentID spawnTool := tools.NewSpawnTool(subagentManager)
spawnTool.SetAllowlistChecker(func(targetAgentID string) bool { currentAgentID := agentID
return registry.CanSpawnSubagent(currentAgentID, targetAgentID) spawnTool.SetAllowlistChecker(func(targetAgentID string) bool {
}) return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
agent.Tools.Register(spawnTool) })
agent.Tools.Register(spawnTool)
}
} }
} }
@ -224,16 +240,18 @@ func (al *AgentLoop) Run(ctx context.Context) error {
if !ok { if !ok {
continue continue
} }
mcpTool := tools.NewMCPTool(mcpManager, serverName, tool) if al.cfg.Tools.IsToolEnabled("mcp") {
agent.Tools.Register(mcpTool) mcpTool := tools.NewMCPTool(mcpManager, serverName, tool)
totalRegistrations++ agent.Tools.Register(mcpTool)
logger.DebugCF("agent", "Registered MCP tool", totalRegistrations++
map[string]any{ logger.DebugCF("agent", "Registered MCP tool",
"agent_id": agentID, map[string]any{
"server": serverName, "agent_id": agentID,
"tool": tool.Name, "server": serverName,
"name": mcpTool.Name(), "tool": tool.Name,
}) "name": mcpTool.Name(),
})
}
} }
} }
} }

View file

@ -523,6 +523,10 @@ type GatewayConfig struct {
Port int `json:"port" env:"PICOCLAW_GATEWAY_PORT"` Port int `json:"port" env:"PICOCLAW_GATEWAY_PORT"`
} }
type ToolConfig struct {
Enabled bool `json:"enabled"`
}
type BraveConfig struct { type BraveConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_BRAVE_ENABLED"` Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_BRAVE_ENABLED"`
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEY"` APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEY"`
@ -558,6 +562,7 @@ type GLMSearchConfig struct {
} }
type WebToolsConfig struct { type WebToolsConfig struct {
ToolConfig
Brave BraveConfig `json:"brave"` Brave BraveConfig `json:"brave"`
Tavily TavilyConfig `json:"tavily"` Tavily TavilyConfig `json:"tavily"`
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"` DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"`
@ -570,19 +575,28 @@ type WebToolsConfig struct {
} }
type CronToolsConfig struct { type CronToolsConfig struct {
ToolConfig
ExecTimeoutMinutes int `json:"exec_timeout_minutes" env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES"` // 0 means no timeout ExecTimeoutMinutes int `json:"exec_timeout_minutes" env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES"` // 0 means no timeout
} }
type ExecConfig struct { type ExecConfig struct {
ToolConfig
EnableDenyPatterns bool `json:"enable_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_ENABLE_DENY_PATTERNS"` EnableDenyPatterns bool `json:"enable_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_ENABLE_DENY_PATTERNS"`
CustomDenyPatterns []string `json:"custom_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_DENY_PATTERNS"` CustomDenyPatterns []string `json:"custom_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_DENY_PATTERNS"`
CustomAllowPatterns []string `json:"custom_allow_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_ALLOW_PATTERNS"` CustomAllowPatterns []string `json:"custom_allow_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_ALLOW_PATTERNS"`
} }
type SkillsToolsConfig struct {
ToolConfig
Registries SkillsRegistriesConfig `json:"registries"`
MaxConcurrentSearches int `json:"max_concurrent_searches" env:"PICOCLAW_SKILLS_MAX_CONCURRENT_SEARCHES"`
SearchCache SearchCacheConfig `json:"search_cache"`
}
type MediaCleanupConfig struct { type MediaCleanupConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_MEDIA_CLEANUP_ENABLED"` ToolConfig
MaxAge int `json:"max_age_minutes" env:"PICOCLAW_MEDIA_CLEANUP_MAX_AGE"` MaxAge int `json:"max_age_minutes" env:"PICOCLAW_MEDIA_CLEANUP_MAX_AGE"`
Interval int `json:"interval_minutes" env:"PICOCLAW_MEDIA_CLEANUP_INTERVAL"` Interval int `json:"interval_minutes" env:"PICOCLAW_MEDIA_CLEANUP_INTERVAL"`
} }
type ToolsConfig struct { type ToolsConfig struct {
@ -594,12 +608,116 @@ type ToolsConfig struct {
Skills SkillsToolsConfig `json:"skills"` Skills SkillsToolsConfig `json:"skills"`
MediaCleanup MediaCleanupConfig `json:"media_cleanup"` MediaCleanup MediaCleanupConfig `json:"media_cleanup"`
MCP MCPConfig `json:"mcp"` MCP MCPConfig `json:"mcp"`
AppendFile ToolConfig `json:"append_file"`
CronTool ToolConfig `json:"cron_tool"`
EditFile ToolConfig `json:"edit_file"`
ExecTool ToolConfig `json:"exec_tool"`
FindSkills ToolConfig `json:"find_skills"`
I2C ToolConfig `json:"i2c"`
InstallSkill ToolConfig `json:"install_skill"`
ListDir ToolConfig `json:"list_dir"`
Message ToolConfig `json:"message"`
ReadFile ToolConfig `json:"read_file"`
Spawn ToolConfig `json:"spawn"`
SPI ToolConfig `json:"spi"`
Subagent ToolConfig `json:"subagent"`
WebFetch ToolConfig `json:"web_fetch"`
WebSearch ToolConfig `json:"web_search"`
WriteFile ToolConfig `json:"write_file"`
} }
type SkillsToolsConfig struct { type ToolsConfigRaw struct {
Registries SkillsRegistriesConfig `json:"registries"` AllowReadPaths []string `json:"allow_read_paths"`
MaxConcurrentSearches int `json:"max_concurrent_searches" env:"PICOCLAW_SKILLS_MAX_CONCURRENT_SEARCHES"` AllowWritePaths []string `json:"allow_write_paths"`
SearchCache SearchCacheConfig `json:"search_cache"` Web WebToolsConfig `json:"web"`
Cron CronToolsConfig `json:"cron"`
Exec ExecConfig `json:"exec"`
Skills SkillsToolsConfig `json:"skills"`
MediaCleanup MediaCleanupConfig `json:"media_cleanup"`
MCP MCPConfig `json:"mcp"`
AppendFile *ToolConfig `json:"append_file,omitempty"`
CronTool *ToolConfig `json:"cron_tool,omitempty"`
EditFile *ToolConfig `json:"edit_file,omitempty"`
ExecTool *ToolConfig `json:"exec_tool,omitempty"`
FindSkills *ToolConfig `json:"find_skills,omitempty"`
I2C *ToolConfig `json:"i2c,omitempty"`
InstallSkill *ToolConfig `json:"install_skill,omitempty"`
ListDir *ToolConfig `json:"list_dir,omitempty"`
Message *ToolConfig `json:"message,omitempty"`
ReadFile *ToolConfig `json:"read_file,omitempty"`
Spawn *ToolConfig `json:"spawn,omitempty"`
SPI *ToolConfig `json:"spi,omitempty"`
Subagent *ToolConfig `json:"subagent,omitempty"`
WebFetch *ToolConfig `json:"web_fetch,omitempty"`
WebSearch *ToolConfig `json:"web_search,omitempty"`
WriteFile *ToolConfig `json:"write_file,omitempty"`
}
func (t *ToolsConfig) UnmarshalJSON(data []byte) error {
var raw ToolsConfigRaw
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
t.AllowReadPaths = raw.AllowReadPaths
t.AllowWritePaths = raw.AllowWritePaths
t.Web = raw.Web
t.Cron = raw.Cron
t.Exec = raw.Exec
t.Skills = raw.Skills
t.MediaCleanup = raw.MediaCleanup
t.MCP = raw.MCP
if raw.AppendFile != nil {
t.AppendFile = *raw.AppendFile
}
if raw.CronTool != nil {
t.CronTool = *raw.CronTool
}
if raw.EditFile != nil {
t.EditFile = *raw.EditFile
}
if raw.ExecTool != nil {
t.ExecTool = *raw.ExecTool
}
if raw.FindSkills != nil {
t.FindSkills = *raw.FindSkills
}
if raw.I2C != nil {
t.I2C = *raw.I2C
}
if raw.InstallSkill != nil {
t.InstallSkill = *raw.InstallSkill
}
if raw.ListDir != nil {
t.ListDir = *raw.ListDir
}
if raw.Message != nil {
t.Message = *raw.Message
}
if raw.ReadFile != nil {
t.ReadFile = *raw.ReadFile
}
if raw.Spawn != nil {
t.Spawn = *raw.Spawn
}
if raw.SPI != nil {
t.SPI = *raw.SPI
}
if raw.Subagent != nil {
t.Subagent = *raw.Subagent
}
if raw.WebFetch != nil {
t.WebFetch = *raw.WebFetch
}
if raw.WebSearch != nil {
t.WebSearch = *raw.WebSearch
}
if raw.WriteFile != nil {
t.WriteFile = *raw.WriteFile
}
return nil
} }
type SearchCacheConfig struct { type SearchCacheConfig struct {
@ -832,3 +950,48 @@ func (c *Config) ValidateModelList() error {
} }
return nil return nil
} }
func (t *ToolsConfig) IsToolEnabled(name string) bool {
switch name {
case "web":
return t.Web.Enabled
case "cron":
return t.Cron.Enabled
case "exec":
return t.Exec.Enabled
case "skills":
return t.Skills.Enabled
case "media_cleanup":
return t.MediaCleanup.Enabled
case "append_file":
return t.AppendFile.Enabled
case "edit_file":
return t.EditFile.Enabled
case "find_skills":
return t.FindSkills.Enabled
case "i2c":
return t.I2C.Enabled
case "install_skill":
return t.InstallSkill.Enabled
case "list_dir":
return t.ListDir.Enabled
case "message":
return t.Message.Enabled
case "read_file":
return t.ReadFile.Enabled
case "spawn":
return t.Spawn.Enabled
case "spi":
return t.SPI.Enabled
case "subagent":
return t.Subagent.Enabled
case "web_fetch":
return t.WebFetch.Enabled
case "web_search":
return t.WebSearch.Enabled
case "write_file":
return t.WriteFile.Enabled
default:
return true
}
}

View file

@ -322,11 +322,16 @@ func DefaultConfig() *Config {
}, },
Tools: ToolsConfig{ Tools: ToolsConfig{
MediaCleanup: MediaCleanupConfig{ MediaCleanup: MediaCleanupConfig{
Enabled: true, ToolConfig: ToolConfig{
Enabled: true,
},
MaxAge: 30, MaxAge: 30,
Interval: 5, Interval: 5,
}, },
Web: WebToolsConfig{ Web: WebToolsConfig{
ToolConfig: ToolConfig{
Enabled: true,
},
Proxy: "", Proxy: "",
FetchLimitBytes: 10 * 1024 * 1024, // 10MB by default FetchLimitBytes: 10 * 1024 * 1024, // 10MB by default
Brave: BraveConfig{ Brave: BraveConfig{
@ -352,12 +357,21 @@ func DefaultConfig() *Config {
}, },
}, },
Cron: CronToolsConfig{ Cron: CronToolsConfig{
ToolConfig: ToolConfig{
Enabled: true,
},
ExecTimeoutMinutes: 5, ExecTimeoutMinutes: 5,
}, },
Exec: ExecConfig{ Exec: ExecConfig{
ToolConfig: ToolConfig{
Enabled: true,
},
EnableDenyPatterns: true, EnableDenyPatterns: true,
}, },
Skills: SkillsToolsConfig{ Skills: SkillsToolsConfig{
ToolConfig: ToolConfig{
Enabled: true,
},
Registries: SkillsRegistriesConfig{ Registries: SkillsRegistriesConfig{
ClawHub: ClawHubRegistryConfig{ ClawHub: ClawHubRegistryConfig{
Enabled: true, Enabled: true,
@ -374,6 +388,54 @@ func DefaultConfig() *Config {
Enabled: false, Enabled: false,
Servers: map[string]MCPServerConfig{}, Servers: map[string]MCPServerConfig{},
}, },
AppendFile: ToolConfig{
Enabled: true,
},
CronTool: ToolConfig{
Enabled: true,
},
EditFile: ToolConfig{
Enabled: true,
},
ExecTool: ToolConfig{
Enabled: true,
},
FindSkills: ToolConfig{
Enabled: true,
},
I2C: ToolConfig{
Enabled: false, // Hardware tool - Linux only
},
InstallSkill: ToolConfig{
Enabled: true,
},
ListDir: ToolConfig{
Enabled: false,
},
Message: ToolConfig{
Enabled: true,
},
ReadFile: ToolConfig{
Enabled: true,
},
Spawn: ToolConfig{
Enabled: true,
},
SPI: ToolConfig{
Enabled: false, // Hardware tool - Linux only
},
Subagent: ToolConfig{
Enabled: true,
},
WebFetch: ToolConfig{
Enabled: true,
},
WebSearch: ToolConfig{
Enabled: true,
},
WriteFile: ToolConfig{
Enabled: true,
},
}, },
Heartbeat: HeartbeatConfig{ Heartbeat: HeartbeatConfig{
Enabled: true, Enabled: true,