feat(config): wire serial tool into runtime and dashboard
This commit is contained in:
parent
0f52076762
commit
2114e1a53f
8 changed files with 93 additions and 0 deletions
|
|
@ -437,6 +437,9 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"mode": "bytes"
|
"mode": "bytes"
|
||||||
},
|
},
|
||||||
|
"serial": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
"send_tts": {
|
"send_tts": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,9 @@ func registerSharedTools(
|
||||||
if cfg.Tools.IsToolEnabled("spi") {
|
if cfg.Tools.IsToolEnabled("spi") {
|
||||||
agent.Tools.Register(tools.NewSPITool())
|
agent.Tools.Register(tools.NewSPITool())
|
||||||
}
|
}
|
||||||
|
if cfg.Tools.IsToolEnabled("serial") {
|
||||||
|
agent.Tools.Register(tools.NewSerialTool())
|
||||||
|
}
|
||||||
|
|
||||||
// Message tool
|
// Message tool
|
||||||
if cfg.Tools.IsToolEnabled("message") {
|
if cfg.Tools.IsToolEnabled("message") {
|
||||||
|
|
|
||||||
|
|
@ -823,6 +823,7 @@ type ToolsConfig struct {
|
||||||
ListDir ToolConfig `json:"list_dir" yaml:"-" envPrefix:"PICOCLAW_TOOLS_LIST_DIR_"`
|
ListDir ToolConfig `json:"list_dir" yaml:"-" envPrefix:"PICOCLAW_TOOLS_LIST_DIR_"`
|
||||||
Message ToolConfig `json:"message" yaml:"-" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
Message ToolConfig `json:"message" yaml:"-" envPrefix:"PICOCLAW_TOOLS_MESSAGE_"`
|
||||||
ReadFile ReadFileToolConfig `json:"read_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
ReadFile ReadFileToolConfig `json:"read_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_READ_FILE_"`
|
||||||
|
Serial ToolConfig `json:"serial" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SERIAL_"`
|
||||||
SendFile ToolConfig `json:"send_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
SendFile ToolConfig `json:"send_file" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_FILE_"`
|
||||||
SendTTS ToolConfig `json:"send_tts" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_TTS_"`
|
SendTTS ToolConfig `json:"send_tts" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SEND_TTS_"`
|
||||||
Spawn ToolConfig `json:"spawn" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
Spawn ToolConfig `json:"spawn" yaml:"-" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
|
||||||
|
|
@ -1548,6 +1549,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
||||||
return t.Message.Enabled
|
return t.Message.Enabled
|
||||||
case "read_file":
|
case "read_file":
|
||||||
return t.ReadFile.Enabled
|
return t.ReadFile.Enabled
|
||||||
|
case "serial":
|
||||||
|
return t.Serial.Enabled
|
||||||
case "spawn":
|
case "spawn":
|
||||||
return t.Spawn.Enabled
|
return t.Spawn.Enabled
|
||||||
case "spawn_status":
|
case "spawn_status":
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,9 @@ func DefaultConfig() *Config {
|
||||||
Mode: ReadFileModeBytes,
|
Mode: ReadFileModeBytes,
|
||||||
MaxReadFileSize: 64 * 1024, // 64KB
|
MaxReadFileSize: 64 * 1024, // 64KB
|
||||||
},
|
},
|
||||||
|
Serial: ToolConfig{
|
||||||
|
Enabled: false, // Hardware tool - requires host serial ports
|
||||||
|
},
|
||||||
Spawn: ToolConfig{
|
Spawn: ToolConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,12 @@ var toolCatalog = []toolCatalogEntry{
|
||||||
Category: "hardware",
|
Category: "hardware",
|
||||||
ConfigKey: "spi",
|
ConfigKey: "spi",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "serial",
|
||||||
|
Description: "Interact with serial ports exposed on the host.",
|
||||||
|
Category: "hardware",
|
||||||
|
ConfigKey: "serial",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "tool_search_tool_regex",
|
Name: "tool_search_tool_regex",
|
||||||
Description: "Discover hidden MCP tools by regex search when tool discovery is enabled.",
|
Description: "Discover hidden MCP tools by regex search when tool discovery is enabled.",
|
||||||
|
|
@ -265,6 +271,8 @@ func buildToolSupport(cfg *config.Config) []toolSupportItem {
|
||||||
status, reasonCode = resolveWebSearchToolSupport(cfg)
|
status, reasonCode = resolveWebSearchToolSupport(cfg)
|
||||||
case "i2c", "spi":
|
case "i2c", "spi":
|
||||||
status, reasonCode = resolveHardwareToolSupport(cfg.Tools.IsToolEnabled(entry.ConfigKey))
|
status, reasonCode = resolveHardwareToolSupport(cfg.Tools.IsToolEnabled(entry.ConfigKey))
|
||||||
|
case "serial":
|
||||||
|
status, reasonCode = resolveSerialToolSupport(cfg.Tools.IsToolEnabled(entry.ConfigKey))
|
||||||
default:
|
default:
|
||||||
if cfg.Tools.IsToolEnabled(entry.ConfigKey) {
|
if cfg.Tools.IsToolEnabled(entry.ConfigKey) {
|
||||||
status = "enabled"
|
status = "enabled"
|
||||||
|
|
@ -293,6 +301,18 @@ func resolveHardwareToolSupport(enabled bool) (string, string) {
|
||||||
return "enabled", ""
|
return "enabled", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveSerialToolSupport(enabled bool) (string, string) {
|
||||||
|
if !enabled {
|
||||||
|
return "disabled", ""
|
||||||
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux", "darwin", "windows":
|
||||||
|
return "enabled", ""
|
||||||
|
default:
|
||||||
|
return "blocked", "requires_serial_platform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDiscoveryToolSupport(cfg *config.Config, methodEnabled bool) (string, string) {
|
func resolveDiscoveryToolSupport(cfg *config.Config, methodEnabled bool) (string, string) {
|
||||||
if !cfg.Tools.IsToolEnabled("mcp") {
|
if !cfg.Tools.IsToolEnabled("mcp") {
|
||||||
return "disabled", ""
|
return "disabled", ""
|
||||||
|
|
@ -362,6 +382,8 @@ func applyToolState(cfg *config.Config, toolName string, enabled bool) error {
|
||||||
cfg.Tools.I2C.Enabled = enabled
|
cfg.Tools.I2C.Enabled = enabled
|
||||||
case "spi":
|
case "spi":
|
||||||
cfg.Tools.SPI.Enabled = enabled
|
cfg.Tools.SPI.Enabled = enabled
|
||||||
|
case "serial":
|
||||||
|
cfg.Tools.Serial.Enabled = enabled
|
||||||
case "tool_search_tool_regex":
|
case "tool_search_tool_regex":
|
||||||
cfg.Tools.MCP.Discovery.UseRegex = enabled
|
cfg.Tools.MCP.Discovery.UseRegex = enabled
|
||||||
if enabled {
|
if enabled {
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,36 @@ func TestHandleListTools(t *testing.T) {
|
||||||
if gotTools["i2c"].Status != "disabled" {
|
if gotTools["i2c"].Status != "disabled" {
|
||||||
t.Fatalf("i2c status = %q, want disabled on linux when config is off", gotTools["i2c"].Status)
|
t.Fatalf("i2c status = %q, want disabled on linux when config is off", gotTools["i2c"].Status)
|
||||||
}
|
}
|
||||||
|
if gotTools["serial"].Status != "disabled" {
|
||||||
|
t.Fatalf("serial status = %q, want disabled when config is off", gotTools["serial"].Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Tools.Serial.Enabled = true
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/tools", nil)
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
gotTools = make(map[string]toolSupportItem, len(resp.Tools))
|
||||||
|
for _, tool := range resp.Tools {
|
||||||
|
gotTools[tool.Name] = tool
|
||||||
|
}
|
||||||
|
if gotTools["serial"].Status != "enabled" {
|
||||||
|
t.Fatalf("serial = %#v, want enabled on linux when config is on", gotTools["serial"])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cfg.Tools.I2C.Enabled = true
|
cfg.Tools.I2C.Enabled = true
|
||||||
cfg.Tools.SPI.Enabled = true
|
cfg.Tools.SPI.Enabled = true
|
||||||
|
cfg.Tools.Serial.Enabled = true
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -120,6 +147,16 @@ func TestHandleListTools(t *testing.T) {
|
||||||
if gotTools["spi"].Status != "blocked" || gotTools["spi"].ReasonCode != "requires_linux" {
|
if gotTools["spi"].Status != "blocked" || gotTools["spi"].ReasonCode != "requires_linux" {
|
||||||
t.Fatalf("spi = %#v, want blocked/requires_linux", gotTools["spi"])
|
t.Fatalf("spi = %#v, want blocked/requires_linux", gotTools["spi"])
|
||||||
}
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin", "windows":
|
||||||
|
if gotTools["serial"].Status != "enabled" {
|
||||||
|
t.Fatalf("serial = %#v, want enabled on supported host", gotTools["serial"])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if gotTools["serial"].Status != "blocked" || gotTools["serial"].ReasonCode != "requires_serial_platform" {
|
||||||
|
t.Fatalf("serial = %#v, want blocked/requires_serial_platform", gotTools["serial"])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +232,26 @@ func TestHandleUpdateToolState(t *testing.T) {
|
||||||
if !updated.Tools.Cron.Enabled {
|
if !updated.Tools.Cron.Enabled {
|
||||||
t.Fatalf("cron should be enabled: %#v", updated.Tools.Cron)
|
t.Fatalf("cron should be enabled: %#v", updated.Tools.Cron)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rec4 := httptest.NewRecorder()
|
||||||
|
req4 := httptest.NewRequest(
|
||||||
|
http.MethodPut,
|
||||||
|
"/api/tools/serial/state",
|
||||||
|
bytes.NewBufferString(`{"enabled":true}`),
|
||||||
|
)
|
||||||
|
req4.Header.Set("Content-Type", "application/json")
|
||||||
|
mux.ServeHTTP(rec4, req4)
|
||||||
|
if rec4.Code != http.StatusOK {
|
||||||
|
t.Fatalf("serial status = %d, want %d, body=%s", rec4.Code, http.StatusOK, rec4.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err = config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig(updated serial) error = %v", err)
|
||||||
|
}
|
||||||
|
if !updated.Tools.Serial.Enabled {
|
||||||
|
t.Fatalf("serial should be enabled: %#v", updated.Tools.Serial)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleListTools_ReportsWebSearchEnabledWhenToolIsOn(t *testing.T) {
|
func TestHandleListTools_ReportsWebSearchEnabledWhenToolIsOn(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -593,6 +593,7 @@
|
||||||
},
|
},
|
||||||
"reasons": {
|
"reasons": {
|
||||||
"requires_linux": "This tool only works on Linux hosts with the required device files exposed.",
|
"requires_linux": "This tool only works on Linux hosts with the required device files exposed.",
|
||||||
|
"requires_serial_platform": "This tool currently supports Linux, macOS, and Windows hosts with accessible serial ports.",
|
||||||
"requires_skills": "Enable `tools.skills` before this skill-registry tool can be used.",
|
"requires_skills": "Enable `tools.skills` before this skill-registry tool can be used.",
|
||||||
"requires_subagent": "Enable `tools.subagent` before the spawn tool can delegate work.",
|
"requires_subagent": "Enable `tools.subagent` before the spawn tool can delegate work.",
|
||||||
"requires_mcp_discovery": "Enable `tools.mcp.discovery` before MCP discovery tools become available.",
|
"requires_mcp_discovery": "Enable `tools.mcp.discovery` before MCP discovery tools become available.",
|
||||||
|
|
|
||||||
|
|
@ -593,6 +593,7 @@
|
||||||
},
|
},
|
||||||
"reasons": {
|
"reasons": {
|
||||||
"requires_linux": "该工具仅在 Linux 主机上可用,并且需要暴露对应的设备文件。",
|
"requires_linux": "该工具仅在 Linux 主机上可用,并且需要暴露对应的设备文件。",
|
||||||
|
"requires_serial_platform": "该工具当前支持 Linux、macOS 和 Windows,且要求主机可访问对应串口。",
|
||||||
"requires_skills": "需要先启用 `tools.skills`,该技能注册表工具才能使用。",
|
"requires_skills": "需要先启用 `tools.skills`,该技能注册表工具才能使用。",
|
||||||
"requires_subagent": "需要先启用 `tools.subagent`,`spawn` 才能委派任务。",
|
"requires_subagent": "需要先启用 `tools.subagent`,`spawn` 才能委派任务。",
|
||||||
"requires_mcp_discovery": "需要先启用 `tools.mcp.discovery`,MCP 发现工具才会可用。",
|
"requires_mcp_discovery": "需要先启用 `tools.mcp.discovery`,MCP 发现工具才会可用。",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue