package service import ( "encoding/json" "fmt" "os" "path" "sort" "strings" "time" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/compose" ) type openclawPluginPackage struct { Version string `json:"version"` } func updateHermesChannelConfig(agent *model.Agent, install *model.AppInstall, write func(confDir string) error) error { if err := write(path.Dir(agent.ConfigPath)); err != nil { return err } _, err := compose.Restart(install.GetComposePath()) return err } func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesFeishuChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractFeishuConfig(conf) installed, _ := checkPluginInstalled(install.GetPath(), "feishu") result.Installed = installed return &result, nil } func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesFeishuChannelConfig(confDir, dto.AgentFeishuConfig{ Enabled: req.Enabled, ThreadSession: req.ThreadSession, ReplyMode: req.ReplyMode, Streaming: req.Streaming, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Domain: "feishu", ConnectionMode: "websocket", Bots: req.Bots, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { config := dto.AgentFeishuConfig{ Enabled: req.Enabled, ThreadSession: req.ThreadSession, ReplyMode: req.ReplyMode, Streaming: req.Streaming, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Domain: req.Domain, ConnectionMode: req.ConnectionMode, Bots: req.Bots, } setFeishuConfig(conf, config) setFeishuPluginEnabled(conf, config.Enabled && hasEnabledBots(config.Bots)) return nil }) } func (a AgentService) GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error) { agent, _, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesTelegramChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractTelegramConfig(conf) return &result, nil } func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesTelegramChannelConfig(confDir, dto.AgentTelegramConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Proxy: req.Proxy, Streaming: req.Streaming, DefaultAccount: req.DefaultAccount, Bots: req.Bots, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setTelegramConfig(conf, dto.AgentTelegramConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Proxy: req.Proxy, Streaming: req.Streaming, DefaultAccount: req.DefaultAccount, Bots: req.Bots, }) return nil }) } func (a AgentService) GetDiscordConfig(req dto.AgentIDReq) (*dto.AgentDiscordConfig, error) { agent, _, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesDiscordChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractDiscordConfig(conf) return &result, nil } func (a AgentService) UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesDiscordChannelConfig(confDir, dto.AgentDiscordConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, Proxy: req.Proxy, DefaultAccount: req.DefaultAccount, Bots: req.Bots, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setDiscordConfig(conf, dto.AgentDiscordConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, RequireMention: req.RequireMention, GroupPolicy: req.GroupPolicy, Proxy: req.Proxy, DefaultAccount: req.DefaultAccount, Bots: req.Bots, }) return nil }) } func (a AgentService) GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesQQBotChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractQQBotConfig(conf) installed, _ := checkPluginInstalled(install.GetPath(), "qqbot") result.Installed = installed return &result, nil } func (a AgentService) UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesQQBotChannelConfig(confDir, dto.AgentQQBotConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Bots: req.Bots, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setQQBotConfig(conf, dto.AgentQQBotConfig{ Enabled: req.Enabled, Bots: req.Bots, }) return nil }) } func (a AgentService) GetWecomConfig(req dto.AgentIDReq) (*dto.AgentWecomConfig, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesWecomChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractWecomConfig(conf) installed, _ := checkPluginInstalled(install.GetPath(), "wecom") result.Installed = installed return &result, nil } func (a AgentService) UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesWecomChannelConfig(confDir, dto.AgentWecomConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, BotID: req.BotID, Secret: req.Secret, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setWecomConfig(conf, dto.AgentWecomConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, BotID: req.BotID, Secret: req.Secret, }) return nil }) } func (a AgentService) GetDingTalkConfig(req dto.AgentIDReq) (*dto.AgentDingTalkConfig, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesDingTalkChannelConfig(path.Dir(agent.ConfigPath)) } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return nil, err } result := extractDingTalkConfig(conf) installed, _ := checkPluginInstalled(install.GetPath(), "dingtalk") result.Installed = installed return &result, nil } func (a AgentService) GetWeixinConfig(req dto.AgentIDReq) (*dto.AgentWeixinConfig, error) { agent, _, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } if agent.AgentType == constant.AppHermesAgent { return readHermesWeixinChannelConfig(path.Dir(agent.ConfigPath)) } return nil, fmt.Errorf("%s does not support", agent.AgentType) } func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { return updateHermesChannelConfig(agent, install, func(confDir string) error { return writeHermesDingTalkChannelConfig(confDir, dto.AgentDingTalkConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, Bots: req.Bots, }) }) } return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setDingTalkConfig(conf, dto.AgentDingTalkConfig{ Enabled: req.Enabled, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, SeparateSessionByConversation: req.SeparateSessionByConversation, GroupSessionScope: req.GroupSessionScope, SharedMemoryAcrossConversations: req.SharedMemoryAcrossConversations, AsyncMode: req.AsyncMode, AckText: req.AckText, Bots: req.Bots, }) return nil }) } func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil { return err } spec, pluginID, err := resolvePluginMeta(req.Type) if err != nil { return err } installTask, err := task.NewTaskWithOps(req.Type, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID) if err != nil { return err } installTask.AddSubTask("Install OpenClaw plugin", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) if req.Type == "qqbot" { legacyPluginPath := path.Join(openclawPluginBaseDir, "qqbot") if err := mgr.Run("docker", "exec", install.ContainerName, "test", "-d", legacyPluginPath); err == nil { if err := uninstallOpenclawPlugin(mgr, install.ContainerName, "qqbot"); err != nil { return err } time.Sleep(2 * time.Second) } } if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return err } appendPluginAllow(conf, pluginID) return writeOpenclawConfigRaw(agent.ConfigPath, conf) }, nil) go func() { if err := installTask.Execute(); err != nil { global.LOG.Errorf("install openclaw plugin failed: %v", err) } }() return nil } func (a AgentService) UpgradePlugin(req dto.AgentPluginUpgradeReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil { return err } spec, pluginID, err := resolvePluginMeta(req.Type) if err != nil { return err } upgradeTask, err := task.NewTaskWithOps(req.Type, task.TaskUpgrade, task.TaskScopeAI, req.TaskID, req.AgentID) if err != nil { return err } upgradeTask.AddSubTask("Upgrade OpenClaw plugin", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil { return err } time.Sleep(2 * time.Second) if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return err } appendPluginAllow(conf, pluginID) return writeOpenclawConfigRaw(agent.ConfigPath, conf) }, nil) go func() { if err := upgradeTask.Execute(); err != nil { global.LOG.Errorf("upgrade openclaw plugin failed: %v", err) } }() return nil } func (a AgentService) UninstallPlugin(req dto.AgentPluginUninstallReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if err := task.CheckScopeTaskIsExecuting(task.TaskScopeAI, req.AgentID); err != nil { return err } _, pluginID, err := resolvePluginMeta(req.Type) if err != nil { return err } uninstallTask, err := task.NewTaskWithOps(req.Type, task.TaskUninstall, task.TaskScopeAI, req.TaskID, req.AgentID) if err != nil { return err } uninstallTask.AddSubTask("Uninstall OpenClaw plugin", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) if err != nil { return err } cleanupOpenclawPluginConfig(conf, req.Type) return writeOpenclawConfigRaw(agent.ConfigPath, conf) }, nil) go func() { if err := uninstallTask.Execute(); err != nil { global.LOG.Errorf("uninstall openclaw plugin failed: %v", err) } }() return nil } func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } loginTask, err := task.NewTaskWithOps("weixin", task.TaskExec, task.TaskScopeAI, req.TaskID, req.AgentID) if err != nil { return err } loginTask.AddSubTask("Login Weixin channel", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(30*time.Minute)) if agent.AgentType == constant.AppHermesAgent { return mgr.Run("docker", buildHermesWeixinLoginArgs(install.ContainerName)...) } return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "channels", "login", "--channel", "openclaw-weixin") }, nil) if agent.AgentType == constant.AppHermesAgent { loginTask.AddSubTask("Restart Hermes-Agent container", func(t *task.Task) error { output, err := compose.Restart(install.GetComposePath()) if output != "" { t.Log(output) } return err }, nil) } go func() { if err := loginTask.Execute(); err != nil { global.LOG.Errorf("login openclaw weixin channel failed: %v", err) } }() return nil } func (a AgentService) DeleteChannelConfig(req dto.AgentChannelDeleteReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType != constant.AppHermesAgent { return fmt.Errorf("%s does not support", agent.AgentType) } return updateHermesChannelConfig(agent, install, func(confDir string) error { switch req.Type { case "telegram": return deleteHermesTelegramChannelConfig(confDir) case "discord": return deleteHermesDiscordChannelConfig(confDir) case "qqbot": return deleteHermesQQBotChannelConfig(confDir) case "wecom": return deleteHermesWecomChannelConfig(confDir) case "dingtalk": return deleteHermesDingTalkChannelConfig(confDir) case "feishu": return deleteHermesFeishuChannelConfig(confDir) case "weixin": return deleteHermesWeixinChannelConfig(confDir) default: return fmt.Errorf("unsupported channel type: %s", req.Type) } }) } func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) { _, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err } installed, err := checkPluginInstalled(install.GetPath(), req.Type) if err != nil { return nil, err } status := &dto.AgentPluginStatus{Installed: installed} if !installed { return status, nil } currentVersion, err := loadOpenclawPluginCurrentVersion(install.GetPath(), req.Type) if err != nil { global.LOG.Errorf("load openclaw plugin current version failed: %v", err) return status, nil } status.CurrentVersion = currentVersion if !req.CheckLatest { return status, nil } latestVersion, err := loadOpenclawPluginLatestVersion(install.ContainerName, req.Type) if err != nil { global.LOG.Errorf("load openclaw plugin latest version failed: %v", err) return status, nil } status.LatestVersion = latestVersion if currentVersion != "" && latestVersion != "" { status.Upgradable = common.CompareVersion(latestVersion, currentVersion) } return status, nil } func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err } if agent.AgentType == constant.AppHermesAgent { mgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) output, err := mgr.RunWithStdout( "docker", buildHermesDockerExecArgs(install.ContainerName, "pairing", "approve", req.Type, req.PairingCode)..., ) return validateHermesPairingApproveResult(output, err) } if req.AccountID != "" { return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run( "docker", "exec", install.ContainerName, "openclaw", "pairing", "approve", req.Type, req.PairingCode, "--account", req.AccountID, ) } return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run( "docker", "exec", install.ContainerName, "openclaw", "pairing", "approve", req.Type, req.PairingCode, ) } func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig { result := dto.AgentFeishuConfig{ Enabled: true, ThreadSession: true, ReplyMode: "auto", Streaming: false, RequireMention: "true", GroupPolicy: "open", GroupAllowFrom: []string{}, Bots: []dto.AgentFeishuBot{}, } feishu := getChannelConfig(conf, "feishu") if len(feishu) == 0 { return result } result.Enabled = extractFeishuPluginEnabled(conf, extractBoolValue(feishu["enabled"], result.Enabled)) if threadSession, ok := feishu["threadSession"].(bool); ok { result.ThreadSession = threadSession } if replyMode := extractStringValue(feishu["replyMode"]); replyMode != "" { result.ReplyMode = replyMode } if streaming, ok := feishu["streaming"].(bool); ok { result.Streaming = streaming } result.RequireMention = extractRequireMentionValue(feishu["requireMention"], result.RequireMention) if groupPolicy := extractStringValue(feishu["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.GroupAllowFrom = extractStringList(feishu["groupAllowFrom"]) defaultBot := defaultFeishuBot() defaultBot.Enabled = extractBoolValue(feishu["enabled"], defaultBot.Enabled) defaultBot.Name = extractDisplayName(feishu, "", "default") defaultBot.AppID = extractStringValue(feishu["appId"]) defaultBot.AppSecret = extractStringValue(feishu["appSecret"]) if dmPolicy := extractStringValue(feishu["dmPolicy"]); dmPolicy != "" { defaultBot.DmPolicy = dmPolicy } if _, ok := feishu["allowFrom"]; ok { defaultBot.AllowFrom = extractStringList(feishu["allowFrom"]) } accounts := childMap(feishu, "accounts") defaultAccount := childMap(accounts, "default") if defaultBot.Name == "Default" { defaultBot.Name = extractDisplayName(defaultAccount, extractStringValue(defaultAccount["botName"]), "Default") } if defaultBot.DmPolicy == "pairing" { if dmPolicy := extractStringValue(defaultAccount["dmPolicy"]); dmPolicy != "" { defaultBot.DmPolicy = dmPolicy } } if len(defaultBot.AllowFrom) == 0 { if _, ok := defaultAccount["allowFrom"]; ok { defaultBot.AllowFrom = extractStringList(defaultAccount["allowFrom"]) } } baseEnabled := defaultBot.Enabled baseDmPolicy := defaultBot.DmPolicy baseAllowFrom := append([]string(nil), defaultBot.AllowFrom...) bots := make([]dto.AgentFeishuBot, 0, len(accounts)+1) if defaultBot.AppID != "" || defaultBot.AppSecret != "" { bots = append(bots, defaultBot) } for _, accountID := range sortedChildKeys(accounts) { if accountID == "default" { continue } account := childMap(accounts, accountID) bot := dto.AgentFeishuBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: accountID, Name: extractDisplayName(account, extractStringValue(account["botName"]), accountID), Enabled: extractBoolValue(account["enabled"], baseEnabled), }, AppID: extractStringValue(account["appId"]), AppSecret: extractStringValue(account["appSecret"]), DmPolicy: baseDmPolicy, AllowFrom: append([]string(nil), baseAllowFrom...), } if dmPolicy := extractStringValue(account["dmPolicy"]); dmPolicy != "" { bot.DmPolicy = dmPolicy } if _, ok := account["allowFrom"]; ok { bot.AllowFrom = extractStringList(account["allowFrom"]) } bots = append(bots, bot) } result.Bots = bots return result } func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) { channels := ensureChildMap(conf, "channels") feishu := ensureChildMap(channels, "feishu") defaultBot := getDefaultFeishuBot(config.Bots) feishu["enabled"] = defaultBot.Enabled feishu["threadSession"] = config.ThreadSession feishu["replyMode"] = config.ReplyMode feishu["streaming"] = config.Streaming if config.RequireMention == "open" { feishu["requireMention"] = "open" } else { feishu["requireMention"] = config.RequireMention == "true" } feishu["groupPolicy"] = config.GroupPolicy if config.GroupPolicy == "allowlist" { feishu["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...) } else { delete(feishu, "groupAllowFrom") } feishu["appId"] = defaultBot.AppID feishu["appSecret"] = defaultBot.AppSecret feishu["dmPolicy"] = defaultBot.DmPolicy if defaultBot.Name != "" && defaultBot.Name != "Default" { feishu["name"] = defaultBot.Name } else { delete(feishu, "name") } if defaultBot.DmPolicy == "open" { feishu["allowFrom"] = []string{"*"} } else if defaultBot.DmPolicy == "allowlist" { feishu["allowFrom"] = append([]string(nil), defaultBot.AllowFrom...) } else { delete(feishu, "allowFrom") } delete(feishu, "botName") delete(feishu, "connectionMode") delete(feishu, "domain") delete(feishu, "webhookPath") delete(feishu, "reactionNotifications") delete(feishu, "typingIndicator") delete(feishu, "resolveSenderNames") delete(feishu, "defaultAccount") accounts := make(map[string]interface{}, len(config.Bots)) for _, bot := range config.Bots { if bot.AccountID == "default" || bot.IsDefault { continue } account := map[string]interface{}{ "enabled": bot.Enabled, "name": bot.Name, "appId": bot.AppID, "appSecret": bot.AppSecret, } if bot.DmPolicy != "" { account["dmPolicy"] = bot.DmPolicy } if bot.DmPolicy == "open" { account["allowFrom"] = []string{"*"} } else if bot.DmPolicy == "allowlist" { account["allowFrom"] = append([]string(nil), bot.AllowFrom...) } accounts[bot.AccountID] = account } if len(accounts) > 0 { feishu["accounts"] = accounts } else { delete(feishu, "accounts") } } func extractFeishuPluginEnabled(conf map[string]interface{}, defaultValue bool) bool { plugins := childMap(conf, "plugins") entries := childMap(plugins, "entries") lark := childMap(entries, "openclaw-lark") return extractBoolValue(lark["enabled"], defaultValue) } func setFeishuPluginEnabled(conf map[string]interface{}, enabled bool) { plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") lark := ensureChildMap(entries, "openclaw-lark") lark["enabled"] = enabled } func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig { result := dto.AgentTelegramConfig{ Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open", GroupAllowFrom: []string{}, Streaming: "partial", } telegram := getChannelConfig(conf, "telegram") if len(telegram) == 0 { return result } if enabled, ok := telegram["enabled"].(bool); ok { result.Enabled = enabled } if dmPolicy := extractStringValue(telegram["dmPolicy"]); dmPolicy != "" { result.DmPolicy = dmPolicy } result.AllowFrom = extractStringList(telegram["allowFrom"]) if groupPolicy := extractStringValue(telegram["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.RequireMention = result.GroupPolicy == "allowlist" result.GroupAllowFrom = extractStringList(telegram["groupAllowFrom"]) result.Proxy = extractStringValue(telegram["proxy"]) result.Streaming = normalizeTelegramStreamingMode(telegram["streaming"], result.Streaming) accounts := childMap(telegram, "accounts") if len(accounts) == 0 { botToken := extractStringValue(telegram["botToken"]) if botToken != "" { accounts["default"] = map[string]interface{}{ "enabled": extractBoolValue(telegram["enabled"], true), "botToken": botToken, "dmPolicy": result.DmPolicy, "groupPolicy": result.GroupPolicy, "streaming": result.Streaming, } } } bots := make([]dto.AgentTelegramBot, 0, len(accounts)) for _, accountID := range sortedChildKeys(accounts) { account := childMap(accounts, accountID) bots = append(bots, dto.AgentTelegramBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: accountID, Name: extractDisplayName(account, accountID, accountID), Enabled: extractBoolValue(account["enabled"], true), }, BotToken: extractStringValue(account["botToken"]), DmPolicy: extractStringValue(account["dmPolicy"]), GroupPolicy: extractStringValue(account["groupPolicy"]), Streaming: normalizeTelegramStreamingMode(account["streaming"], result.Streaming), }) } result.DefaultAccount = normalizeDefaultAccount(extractStringValue(telegram["defaultAccount"]), getTelegramBotAccountIDs(bots)) setTelegramDefaultFlags(bots, result.DefaultAccount) result.Bots = bots return result } func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConfig) { channels := ensureChildMap(conf, "channels") telegram := ensureChildMap(channels, "telegram") defaultAccount := normalizeDefaultAccount(config.DefaultAccount, getTelegramBotAccountIDs(config.Bots)) effectiveEnabled := config.Enabled && hasEnabledBots(config.Bots) telegram["enabled"] = effectiveEnabled telegram["dmPolicy"] = config.DmPolicy telegram["groupPolicy"] = config.GroupPolicy telegram["defaultAccount"] = defaultAccount if config.RequireMention { telegram["groupPolicy"] = "allowlist" } else if config.GroupPolicy == "allowlist" { telegram["groupPolicy"] = "allowlist" } if config.DmPolicy == "open" { telegram["allowFrom"] = []string{"*"} } else if config.DmPolicy == "allowlist" { telegram["allowFrom"] = append([]string(nil), config.AllowFrom...) } else { delete(telegram, "allowFrom") } if config.GroupPolicy == "allowlist" { telegram["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...) } else { delete(telegram, "groupAllowFrom") } if config.Proxy != "" { telegram["proxy"] = config.Proxy } else { delete(telegram, "proxy") } telegram["streaming"] = buildTelegramStreamingConfig(config.Streaming) accounts := make(map[string]interface{}, len(config.Bots)) for _, bot := range config.Bots { account := map[string]interface{}{ "enabled": bot.Enabled, "name": bot.Name, "botToken": bot.BotToken, "dmPolicy": bot.DmPolicy, "groupPolicy": bot.GroupPolicy, "streaming": buildTelegramStreamingConfig(bot.Streaming), } if bot.DmPolicy == "open" { account["allowFrom"] = []string{"*"} } accounts[bot.AccountID] = account } telegram["accounts"] = accounts delete(telegram, "botToken") } func normalizeTelegramStreamingMode(value interface{}, defaultMode string) string { mode := defaultMode switch typed := value.(type) { case string: mode = typed case map[string]interface{}: mode = extractStringValue(typed["mode"]) } mode = strings.ToLower(strings.TrimSpace(mode)) switch mode { case "off", "partial", "block", "progress": return mode default: return "partial" } } func buildTelegramStreamingConfig(mode string) map[string]interface{} { return map[string]interface{}{ "mode": normalizeTelegramStreamingMode(mode, "partial"), } } func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig { result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, RequireMention: false, GroupPolicy: "open"} discord := getChannelConfig(conf, "discord") if len(discord) == 0 { return result } if enabled, ok := discord["enabled"].(bool); ok { result.Enabled = enabled } if dmPolicy := extractStringValue(discord["dmPolicy"]); dmPolicy != "" { result.DmPolicy = dmPolicy } else if dm := childMap(discord, "dm"); dm != nil { if policy := extractStringValue(dm["policy"]); policy != "" { result.DmPolicy = policy } } if groupPolicy := extractStringValue(discord["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.RequireMention = result.GroupPolicy == "allowlist" result.AllowFrom = extractStringList(discord["allowFrom"]) result.Proxy = extractStringValue(discord["proxy"]) accounts := childMap(discord, "accounts") if len(accounts) == 0 { token := extractStringValue(discord["token"]) if token != "" { accounts["default"] = map[string]interface{}{ "enabled": extractBoolValue(discord["enabled"], true), "token": token, } } } bots := make([]dto.AgentDiscordBot, 0, len(accounts)) for _, accountID := range sortedChildKeys(accounts) { account := childMap(accounts, accountID) bots = append(bots, dto.AgentDiscordBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: accountID, Name: extractDisplayName(account, accountID, accountID), Enabled: extractBoolValue(account["enabled"], true), }, Token: extractStringValue(account["token"]), }) } result.DefaultAccount = normalizeDefaultAccount(extractStringValue(discord["defaultAccount"]), getDiscordBotAccountIDs(bots)) setDiscordDefaultFlags(bots, result.DefaultAccount) result.Bots = bots return result } func setDiscordConfig(conf map[string]interface{}, config dto.AgentDiscordConfig) { channels := ensureChildMap(conf, "channels") discord := ensureChildMap(channels, "discord") defaultAccount := normalizeDefaultAccount(config.DefaultAccount, getDiscordBotAccountIDs(config.Bots)) effectiveEnabled := config.Enabled && hasEnabledBots(config.Bots) discord["enabled"] = effectiveEnabled discord["dmPolicy"] = config.DmPolicy discord["groupPolicy"] = config.GroupPolicy discord["defaultAccount"] = defaultAccount if config.RequireMention { discord["groupPolicy"] = "allowlist" } else if config.GroupPolicy == "allowlist" { discord["groupPolicy"] = "allowlist" } if config.DmPolicy == "open" { discord["allowFrom"] = []string{"*"} } else if config.DmPolicy == "allowlist" { discord["allowFrom"] = append([]string(nil), config.AllowFrom...) } else { delete(discord, "allowFrom") } if config.Proxy != "" { discord["proxy"] = config.Proxy } else { delete(discord, "proxy") } accounts := make(map[string]interface{}, len(config.Bots)) for _, bot := range config.Bots { accounts[bot.AccountID] = map[string]interface{}{ "enabled": bot.Enabled, "name": bot.Name, "token": bot.Token, "groupPolicy": config.GroupPolicy, } } discord["accounts"] = accounts delete(discord, "token") delete(discord, "dm") } func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig { result := dto.AgentQQBotConfig{Enabled: true} qqbot := getChannelConfig(conf, "qqbot") if len(qqbot) == 0 { result.Bots = []dto.AgentQQBotBot{} return result } if enabled, ok := qqbot["enabled"].(bool); ok { result.Enabled = enabled } bots := make([]dto.AgentQQBotBot, 0, len(childMap(qqbot, "accounts"))+1) defaultBot := dto.AgentQQBotBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: "default", Name: extractStringValue(qqbot["name"]), Enabled: extractBoolValue(qqbot["enabled"], true), IsDefault: true, }, AppID: extractStringValue(qqbot["appId"]), ClientSecret: extractStringValue(qqbot["clientSecret"]), AllowFrom: extractStringList(qqbot["allowFrom"]), SystemPrompt: extractStringValue(qqbot["systemPrompt"]), } if defaultBot.Name == "" { defaultBot.Name = "Default" } if defaultBot.AppID != "" || defaultBot.ClientSecret != "" { bots = append(bots, defaultBot) } accounts := childMap(qqbot, "accounts") for _, accountID := range sortedChildKeys(accounts) { account := childMap(accounts, accountID) bots = append(bots, dto.AgentQQBotBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: accountID, Name: extractDisplayName(account, accountID, accountID), Enabled: extractBoolValue(account["enabled"], true), }, AppID: extractStringValue(account["appId"]), ClientSecret: extractStringValue(account["clientSecret"]), AllowFrom: extractStringList(account["allowFrom"]), SystemPrompt: extractStringValue(account["systemPrompt"]), }) } result.Bots = bots return result } func extractWecomConfig(conf map[string]interface{}) dto.AgentWecomConfig { result := dto.AgentWecomConfig{ Enabled: true, DmPolicy: "pairing", AllowFrom: []string{}, GroupPolicy: "open", GroupAllowFrom: []string{}, } channels, ok := conf["channels"].(map[string]interface{}) if !ok { return result } wecom, ok := channels["wecom"].(map[string]interface{}) if !ok { return result } if enabled, ok := wecom["enabled"].(bool); ok { result.Enabled = enabled } if dmPolicy := extractStringValue(wecom["dmPolicy"]); dmPolicy != "" { result.DmPolicy = dmPolicy } result.AllowFrom = extractStringList(wecom["allowFrom"]) if groupPolicy := extractStringValue(wecom["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.GroupAllowFrom = extractStringList(wecom["groupAllowFrom"]) result.BotID = extractStringValue(wecom["botId"]) result.Secret = extractStringValue(wecom["secret"]) return result } func extractDingTalkConfig(conf map[string]interface{}) dto.AgentDingTalkConfig { result := dto.AgentDingTalkConfig{ Enabled: true, DmPolicy: "open", GroupPolicy: "disabled", AllowFrom: []string{}, GroupAllowFrom: []string{}, SeparateSessionByConversation: true, GroupSessionScope: "group", SharedMemoryAcrossConversations: false, AsyncMode: false, AckText: "任务已接收,处理中...", } dingtalk := getChannelConfig(conf, "dingtalk-connector") if len(dingtalk) == 0 { return result } if enabled, ok := dingtalk["enabled"].(bool); ok { result.Enabled = enabled } if dmPolicy := extractStringValue(dingtalk["dmPolicy"]); dmPolicy != "" { if dmPolicy == "pairing" { result.DmPolicy = "open" } else { result.DmPolicy = dmPolicy } } if groupPolicy := extractStringValue(dingtalk["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.AllowFrom = extractStringList(dingtalk["allowFrom"]) result.GroupAllowFrom = extractStringList(dingtalk["groupAllowFrom"]) if separateSessionByConversation, ok := dingtalk["separateSessionByConversation"].(bool); ok { result.SeparateSessionByConversation = separateSessionByConversation } if groupSessionScope := extractStringValue(dingtalk["groupSessionScope"]); groupSessionScope != "" { result.GroupSessionScope = groupSessionScope } if sharedMemoryAcrossConversations, ok := dingtalk["sharedMemoryAcrossConversations"].(bool); ok { result.SharedMemoryAcrossConversations = sharedMemoryAcrossConversations } if asyncMode, ok := dingtalk["asyncMode"].(bool); ok { result.AsyncMode = asyncMode } if ackText := extractStringValue(dingtalk["ackText"]); ackText != "" { result.AckText = ackText } accounts := childMap(dingtalk, "accounts") if len(accounts) == 0 { clientID := extractStringValue(dingtalk["clientId"]) clientSecret := extractStringValue(dingtalk["clientSecret"]) if clientID != "" || clientSecret != "" { accounts["default"] = map[string]interface{}{ "enabled": extractBoolValue(dingtalk["enabled"], true), "clientId": clientID, "clientSecret": clientSecret, } } } bots := make([]dto.AgentDingTalkBot, 0, len(accounts)) for _, accountID := range sortedChildKeys(accounts) { account := childMap(accounts, accountID) bots = append(bots, dto.AgentDingTalkBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: accountID, Name: accountID, Enabled: extractBoolValue(account["enabled"], true), }, ClientID: extractStringValue(account["clientId"]), ClientSecret: extractStringValue(account["clientSecret"]), }) } result.Bots = bots return result } func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) { channels := ensureChildMap(conf, "channels") wecom := ensureChildMap(channels, "wecom") wecom["enabled"] = config.Enabled wecom["botId"] = config.BotID wecom["secret"] = config.Secret wecom["dmPolicy"] = config.DmPolicy wecom["groupPolicy"] = config.GroupPolicy wecom["sendThinkingMessage"] = true if config.DmPolicy == "allowlist" { wecom["allowFrom"] = append([]string(nil), config.AllowFrom...) } else { delete(wecom, "allowFrom") } if config.GroupPolicy == "allowlist" { wecom["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...) } else { delete(wecom, "groupAllowFrom") } plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") wecomEntry := ensureChildMap(entries, "wecom-openclaw-plugin") wecomEntry["enabled"] = config.Enabled } func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConfig) { channels := ensureChildMap(conf, "channels") dingtalk := ensureChildMap(channels, "dingtalk-connector") effectiveEnabled := config.Enabled && hasEnabledBots(config.Bots) dingtalk["enabled"] = effectiveEnabled dingtalk["dmPolicy"] = config.DmPolicy dingtalk["groupPolicy"] = config.GroupPolicy dingtalk["separateSessionByConversation"] = config.SeparateSessionByConversation dingtalk["groupSessionScope"] = config.GroupSessionScope dingtalk["sharedMemoryAcrossConversations"] = config.SharedMemoryAcrossConversations dingtalk["asyncMode"] = config.AsyncMode dingtalk["ackText"] = config.AckText delete(dingtalk, "gatewayToken") switch config.DmPolicy { case "open": dingtalk["allowFrom"] = []string{"*"} case "allowlist": dingtalk["allowFrom"] = append([]string(nil), config.AllowFrom...) default: delete(dingtalk, "allowFrom") } switch config.GroupPolicy { case "open": dingtalk["groupAllowFrom"] = []string{"*"} case "allowlist": dingtalk["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...) default: delete(dingtalk, "groupAllowFrom") } accounts := make(map[string]interface{}, len(config.Bots)) for _, bot := range config.Bots { accounts[bot.AccountID] = map[string]interface{}{ "enabled": bot.Enabled, "clientId": bot.ClientID, "clientSecret": bot.ClientSecret, } } dingtalk["accounts"] = accounts delete(dingtalk, "clientId") delete(dingtalk, "clientSecret") plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") dingtalkEntry := ensureChildMap(entries, "dingtalk-connector") dingtalkEntry["enabled"] = effectiveEnabled gateway := ensureChildMap(conf, "gateway") httpMap := ensureChildMap(gateway, "http") endpoints := ensureChildMap(httpMap, "endpoints") chatCompletions := ensureChildMap(endpoints, "chatCompletions") chatCompletions["enabled"] = true } func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) { channels := ensureChildMap(conf, "channels") qqbot := ensureChildMap(channels, "qqbot") defaultBot := getDefaultQQBot(config.Bots) effectiveEnabled := config.Enabled && hasEnabledBots(config.Bots) delete(qqbot, "dmPolicy") qqbot["enabled"] = effectiveEnabled qqbot["appId"] = defaultBot.AppID qqbot["clientSecret"] = defaultBot.ClientSecret qqbot["name"] = defaultBot.Name if len(defaultBot.AllowFrom) > 0 { qqbot["allowFrom"] = append([]string(nil), defaultBot.AllowFrom...) } else { delete(qqbot, "allowFrom") } if defaultBot.SystemPrompt != "" { qqbot["systemPrompt"] = defaultBot.SystemPrompt } else { delete(qqbot, "systemPrompt") } accounts := make(map[string]interface{}, len(config.Bots)) for _, bot := range config.Bots { if bot.AccountID == "default" || bot.IsDefault { continue } account := map[string]interface{}{ "enabled": bot.Enabled, "name": bot.Name, "appId": bot.AppID, "clientSecret": bot.ClientSecret, } if len(bot.AllowFrom) > 0 { account["allowFrom"] = append([]string(nil), bot.AllowFrom...) } if bot.SystemPrompt != "" { account["systemPrompt"] = bot.SystemPrompt } accounts[bot.AccountID] = account } qqbot["accounts"] = accounts plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") delete(entries, "qqbot") qqbotEntry := ensureChildMap(entries, "openclaw-qqbot") qqbotEntry["enabled"] = effectiveEnabled } func appendPluginAllow(conf map[string]interface{}, pluginID string) { plugins := ensureChildMap(conf, "plugins") allow := make([]string, 0, 4) seen := map[string]struct{}{} switch values := plugins["allow"].(type) { case []interface{}: for _, value := range values { text, ok := value.(string) if !ok || text == "" { continue } if _, ok := seen[text]; ok { continue } seen[text] = struct{}{} allow = append(allow, text) } } if _, ok := seen[pluginID]; ok { plugins["allow"] = allow return } plugins["allow"] = append(allow, pluginID) } func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error { workdir := path.Join(openclawPluginPackageTmpDir, pluginID) defer func() { _ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir) }() if err := mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir); err != nil { return err } if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", workdir); err != nil { return err } if err := mgr.Run("docker", "exec", "-w", workdir, containerName, "npm", "pack", "--silent", spec); err != nil { return err } pkgPath, err := cmd.RunDockerExecWithStdout(10*time.Minute, containerName, "find", workdir, "-maxdepth", "1", "-type", "f", "-name", "*.tgz", "-print", "-quit") if err != nil { return err } pkgPath = strings.TrimSpace(pkgPath) if pkgPath == "" { return fmt.Errorf("openclaw plugin package not found") } return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install") } func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error { _, err := mgr.RunPipe( cmd.PipeCommand{Name: "printf", Args: []string{"yes\n"}}, cmd.PipeCommand{Name: "docker", Args: []string{"exec", "-i", containerName, "openclaw", "plugins", "uninstall", pluginID}}, ) if err != nil && strings.Contains(err.Error(), "exit status 137") { return nil } return err } func resolvePluginMeta(pluginType string) (string, string, error) { switch pluginType { case "qqbot": return "@tencent-connect/openclaw-qqbot", "openclaw-qqbot", nil case "feishu": return "@larksuite/openclaw-lark", "openclaw-lark", nil case "wecom": return "@wecom/wecom-openclaw-plugin", "wecom-openclaw-plugin", nil case "dingtalk": return "@dingtalk-real-ai/dingtalk-connector", "dingtalk-connector", nil case "weixin": return "@tencent-weixin/openclaw-weixin", "openclaw-weixin", nil default: return "", "", fmt.Errorf("unsupported plugin type") } } func checkPluginInstalled(installPath, pluginType string) (bool, error) { packagePath, err := resolveOpenclawPluginPackagePath(installPath, pluginType) if err != nil { return false, err } if _, err := os.Stat(packagePath); err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return true, nil } func loadOpenclawPluginCurrentVersion(installPath, pluginType string) (string, error) { packagePath, err := resolveOpenclawPluginPackagePath(installPath, pluginType) if err != nil { return "", err } content, err := os.ReadFile(packagePath) if err != nil { return "", err } var pkg openclawPluginPackage if err := json.Unmarshal(content, &pkg); err != nil { return "", err } return pkg.Version, nil } func loadOpenclawPluginLatestVersion(containerName, pluginType string) (string, error) { spec, _, err := resolvePluginMeta(pluginType) if err != nil { return "", err } output, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json") if err != nil { return "", err } var version string if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &version); err == nil { return version, nil } return strings.Trim(strings.TrimSpace(output), `"`), nil } func resolveOpenclawPluginPackagePath(installPath, pluginType string) (string, error) { _, pluginID, err := resolvePluginMeta(pluginType) if err != nil { return "", err } if installPath == "" { return "", buserr.New("ErrRecordNotFound") } return path.Join(installPath, "data", "conf", "extensions", pluginID, "package.json"), nil } func cleanupOpenclawPluginConfig(conf map[string]interface{}, pluginType string) { channels, _ := conf["channels"].(map[string]interface{}) plugins, _ := conf["plugins"].(map[string]interface{}) entries, _ := plugins["entries"].(map[string]interface{}) switch pluginType { case "feishu": delete(channels, "feishu") delete(entries, "openclaw-lark") delete(entries, "feishu") case "qqbot": delete(channels, "qqbot") delete(entries, "openclaw-qqbot") delete(entries, "qqbot") case "wecom": delete(channels, "wecom") delete(entries, "wecom-openclaw-plugin") case "dingtalk": delete(channels, "dingtalk-connector") delete(entries, "dingtalk-connector") gateway := ensureChildMap(conf, "gateway") httpMap := ensureChildMap(gateway, "http") endpoints := ensureChildMap(httpMap, "endpoints") chatCompletions := ensureChildMap(endpoints, "chatCompletions") chatCompletions["enabled"] = false case "weixin": delete(channels, "weixin") delete(entries, "openclaw-weixin") } } func getChannelConfig(conf map[string]interface{}, channel string) map[string]interface{} { channels, ok := conf["channels"].(map[string]interface{}) if !ok { return nil } channelMap, _ := channels[channel].(map[string]interface{}) return channelMap } func childMap(parent map[string]interface{}, key string) map[string]interface{} { if parent == nil { return map[string]interface{}{} } if value, ok := parent[key].(map[string]interface{}); ok { return value } return map[string]interface{}{} } func extractStringValue(value interface{}) string { text, _ := value.(string) return text } func extractRequireMentionValue(value interface{}, defaultValue string) string { switch typed := value.(type) { case bool: if typed { return "true" } return "false" case string: if typed != "" { return typed } } return defaultValue } func extractBoolValue(value interface{}, defaultValue bool) bool { result, ok := value.(bool) if !ok { return defaultValue } return result } func sortedChildKeys(values map[string]interface{}) []string { keys := make([]string, 0, len(values)) for key := range values { keys = append(keys, key) } sort.Strings(keys) return keys } func extractDisplayName(values map[string]interface{}, fallback string, accountID string) string { name := extractStringValue(values["name"]) if name != "" { return name } if fallback != "" { return fallback } if accountID == "default" { return "Default" } return accountID } func normalizeDefaultAccount(defaultAccount string, accountIDs []string) string { if len(accountIDs) == 0 { return "" } for _, accountID := range accountIDs { if accountID == defaultAccount { return defaultAccount } } return accountIDs[0] } func getTelegramBotAccountIDs(bots []dto.AgentTelegramBot) []string { accountIDs := make([]string, 0, len(bots)) for _, bot := range bots { accountIDs = append(accountIDs, bot.AccountID) } return accountIDs } func getDiscordBotAccountIDs(bots []dto.AgentDiscordBot) []string { accountIDs := make([]string, 0, len(bots)) for _, bot := range bots { accountIDs = append(accountIDs, bot.AccountID) } return accountIDs } func setTelegramDefaultFlags(bots []dto.AgentTelegramBot, defaultAccount string) { for i := range bots { bots[i].IsDefault = bots[i].AccountID == defaultAccount } } func setDiscordDefaultFlags(bots []dto.AgentDiscordBot, defaultAccount string) { for i := range bots { bots[i].IsDefault = bots[i].AccountID == defaultAccount } } type enabledBot interface { IsEnabled() bool } func hasEnabledBots[T enabledBot](bots []T) bool { for _, bot := range bots { if bot.IsEnabled() { return true } } return false } func defaultQQBot() dto.AgentQQBotBot { return dto.AgentQQBotBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: "default", Name: "Default", Enabled: true, IsDefault: true, }, } } func defaultFeishuBot() dto.AgentFeishuBot { return dto.AgentFeishuBot{ AgentChannelBotBase: dto.AgentChannelBotBase{ AccountID: "default", Name: "Default", Enabled: true, IsDefault: true, }, DmPolicy: "pairing", AllowFrom: []string{}, } } func getDefaultFeishuBot(bots []dto.AgentFeishuBot) dto.AgentFeishuBot { for _, bot := range bots { if bot.IsDefault || bot.AccountID == "default" { bot.IsDefault = true if bot.Name == "" { bot.Name = "Default" } return bot } } if len(bots) > 0 { bot := bots[0] bot.IsDefault = true if bot.AccountID == "" { bot.AccountID = "default" } if bot.Name == "" { bot.Name = "Default" } return bot } return defaultFeishuBot() } func getDefaultQQBot(bots []dto.AgentQQBotBot) dto.AgentQQBotBot { for _, bot := range bots { if bot.IsDefault || bot.AccountID == "default" { bot.IsDefault = true if bot.Name == "" { bot.Name = "Default" } return bot } } if len(bots) > 0 { bot := bots[0] bot.IsDefault = true if bot.Name == "" { bot.Name = "Default" } return bot } return defaultQQBot() }