From 538415bca5101b8c4b892ae3e3324026a90fe527 Mon Sep 17 00:00:00 2001 From: SiYue-ZO <2835601846@qq.com> Date: Wed, 25 Mar 2026 01:00:22 +0800 Subject: [PATCH] backend: add unified restart-required signature logic - Add bootConfigSignature to gateway state for tracking restart-sensitive config - Implement computeConfigSignature() to capture model and tool enabled states - Add gatewayRestartRequiredBySignature() for unified restart detection - Update gatewayStatusData() to use signature-based comparison - Preserve backward compatibility with existing boot_default_model field --- web/backend/api/gateway.go | 120 +++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 12 deletions(-) diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index 4bde5ce82..25193d345 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -25,13 +25,14 @@ import ( // gateway holds the state for the managed gateway process. var gateway = struct { - mu sync.Mutex - cmd *exec.Cmd - owned bool // true if we started the process, false if we attached to an existing one - bootDefaultModel string - runtimeStatus string - startupDeadline time.Time - logs *LogBuffer + mu sync.Mutex + cmd *exec.Cmd + owned bool // true if we started the process, false if we attached to an existing one + bootDefaultModel string + bootConfigSignature string + runtimeStatus string + startupDeadline time.Time + logs *LogBuffer }{ runtimeStatus: "stopped", logs: NewLogBuffer(200), @@ -187,6 +188,95 @@ func gatewayRestartRequired(configDefaultModel, bootDefaultModel, gatewayStatus return configDefaultModel != bootDefaultModel } +func computeConfigSignature(cfg *config.Config) string { + if cfg == nil { + return "" + } + var parts []string + defaultModel := strings.TrimSpace(cfg.Agents.Defaults.GetModelName()) + if defaultModel != "" { + parts = append(parts, "model:"+defaultModel) + } + toolSignatures := []string{} + if cfg.Tools.ReadFile.Enabled { + toolSignatures = append(toolSignatures, "read_file") + } + if cfg.Tools.WriteFile.Enabled { + toolSignatures = append(toolSignatures, "write_file") + } + if cfg.Tools.ListDir.Enabled { + toolSignatures = append(toolSignatures, "list_dir") + } + if cfg.Tools.EditFile.Enabled { + toolSignatures = append(toolSignatures, "edit_file") + } + if cfg.Tools.AppendFile.Enabled { + toolSignatures = append(toolSignatures, "append_file") + } + if cfg.Tools.Exec.Enabled { + toolSignatures = append(toolSignatures, "exec") + } + if cfg.Tools.Cron.Enabled { + toolSignatures = append(toolSignatures, "cron") + } + if cfg.Tools.Web.Enabled { + toolSignatures = append(toolSignatures, "web") + } + if cfg.Tools.WebFetch.Enabled { + toolSignatures = append(toolSignatures, "web_fetch") + } + if cfg.Tools.Message.Enabled { + toolSignatures = append(toolSignatures, "message") + } + if cfg.Tools.SendFile.Enabled { + toolSignatures = append(toolSignatures, "send_file") + } + if cfg.Tools.FindSkills.Enabled { + toolSignatures = append(toolSignatures, "find_skills") + } + if cfg.Tools.InstallSkill.Enabled { + toolSignatures = append(toolSignatures, "install_skill") + } + if cfg.Tools.Spawn.Enabled { + toolSignatures = append(toolSignatures, "spawn") + } + if cfg.Tools.SpawnStatus.Enabled { + toolSignatures = append(toolSignatures, "spawn_status") + } + if cfg.Tools.I2C.Enabled { + toolSignatures = append(toolSignatures, "i2c") + } + if cfg.Tools.SPI.Enabled { + toolSignatures = append(toolSignatures, "spi") + } + if cfg.Tools.MCP.Enabled { + toolSignatures = append(toolSignatures, "mcp") + } + if cfg.Tools.MCP.Discovery.Enabled { + toolSignatures = append(toolSignatures, "mcp_discovery") + } + if cfg.Tools.MCP.Discovery.UseRegex { + toolSignatures = append(toolSignatures, "mcp_discovery_regex") + } + if cfg.Tools.MCP.Discovery.UseBM25 { + toolSignatures = append(toolSignatures, "mcp_discovery_bm25") + } + if len(toolSignatures) > 0 { + parts = append(parts, "tools:"+strings.Join(toolSignatures, ",")) + } + return strings.Join(parts, ";") +} + +func gatewayRestartRequiredBySignature(bootSignature, currentSignature, gatewayStatus string) bool { + if gatewayStatus != "running" { + return false + } + if bootSignature == "" || currentSignature == "" { + return false + } + return bootSignature != currentSignature +} + func isCmdProcessAliveLocked(cmd *exec.Cmd) bool { if cmd == nil || cmd.Process == nil { return false @@ -228,10 +318,11 @@ func attachToGatewayProcessLocked(pid int, cfg *config.Config) error { gateway.owned = false // We didn't start this process setGatewayRuntimeStatusLocked("running") - // Update bootDefaultModel from config + // Update bootDefaultModel and bootConfigSignature from config if cfg != nil { defaultModelName := strings.TrimSpace(cfg.Agents.Defaults.GetModelName()) gateway.bootDefaultModel = defaultModelName + gateway.bootConfigSignature = computeConfigSignature(cfg) } logger.InfoC("gateway", fmt.Sprintf("Attached to gateway process (PID: %d)", pid)) @@ -419,6 +510,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int gateway.cmd = cmd gateway.owned = true // We started this process gateway.bootDefaultModel = defaultModelName + gateway.bootConfigSignature = computeConfigSignature(cfg) setGatewayRuntimeStatusLocked(initialStatus) pid = cmd.Process.Pid logger.InfoC("gateway", fmt.Sprintf("Started picoclaw gateway (PID: %d) from %s", pid, execPath)) @@ -439,6 +531,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int if gateway.cmd == cmd { gateway.cmd = nil gateway.bootDefaultModel = "" + gateway.bootConfigSignature = "" if gateway.runtimeStatus != "restarting" { setGatewayRuntimeStatusLocked("stopped") } @@ -784,11 +877,14 @@ func (h *Handler) gatewayStatusData() map[string]any { } } - bootDefaultModel, _ := data["boot_default_model"].(string) gatewayStatus, _ := data["gateway_status"].(string) - data["gateway_restart_required"] = gatewayRestartRequired( - configDefaultModel, - bootDefaultModel, + currentConfigSignature := computeConfigSignature(cfg) + gateway.mu.Lock() + bootConfigSignature := gateway.bootConfigSignature + gateway.mu.Unlock() + data["gateway_restart_required"] = gatewayRestartRequiredBySignature( + bootConfigSignature, + currentConfigSignature, gatewayStatus, )