diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index 3ff4e4f83d25..cbbef8f4bb99 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -268,12 +268,9 @@ type AgentFeishuConfigReq struct { } type AgentFeishuConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - BotName string `json:"botName" validate:"required"` - AppID string `json:"appId" validate:"required"` - AppSecret string `json:"appSecret" validate:"required"` - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy" validate:"required"` + AgentID uint `json:"agentId" validate:"required"` + Enabled bool `json:"enabled"` + Bots []AgentFeishuBot `json:"bots" validate:"required,min=1"` } type AgentFeishuPairingApproveReq struct { @@ -282,11 +279,9 @@ type AgentFeishuPairingApproveReq struct { } type AgentFeishuConfig struct { - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy"` - BotName string `json:"botName"` - AppID string `json:"appId"` - AppSecret string `json:"appSecret"` + Enabled bool `json:"enabled"` + Bots []AgentFeishuBot `json:"bots"` + Installed bool `json:"installed"` } type AgentTelegramConfigReq struct { @@ -294,24 +289,27 @@ type AgentTelegramConfigReq struct { } type AgentTelegramConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy" validate:"required"` - BotToken string `json:"botToken" validate:"required"` - Proxy string `json:"proxy"` + AgentID uint `json:"agentId" validate:"required"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy" validate:"required"` + Proxy string `json:"proxy"` + DefaultAccount string `json:"defaultAccount" validate:"required"` + Bots []AgentTelegramBot `json:"bots" validate:"required,min=1"` } type AgentTelegramConfig struct { - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy"` - BotToken string `json:"botToken"` - Proxy string `json:"proxy"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy"` + Proxy string `json:"proxy"` + DefaultAccount string `json:"defaultAccount"` + Bots []AgentTelegramBot `json:"bots"` } type AgentChannelPairingApproveReq struct { AgentID uint `json:"agentId" validate:"required"` - Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom dingtalk-connector"` + Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom"` PairingCode string `json:"pairingCode" validate:"required"` + AccountID string `json:"accountId"` } type AgentWecomConfigUpdateReq struct { @@ -331,25 +329,23 @@ type AgentWecomConfig struct { } type AgentDingTalkConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - Enabled bool `json:"enabled"` - ClientID string `json:"clientId" validate:"required"` - ClientSecret string `json:"clientSecret" validate:"required"` - DmPolicy string `json:"dmPolicy" validate:"required,oneof=pairing allowlist open disabled"` - AllowFrom []string `json:"allowFrom"` - GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"` - GroupAllowFrom []string `json:"groupAllowFrom"` + AgentID uint `json:"agentId" validate:"required"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy" validate:"required,oneof=allowlist open disabled"` + AllowFrom []string `json:"allowFrom"` + GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"` + GroupAllowFrom []string `json:"groupAllowFrom"` + Bots []AgentDingTalkBot `json:"bots" validate:"required,min=1"` } type AgentDingTalkConfig struct { - Enabled bool `json:"enabled"` - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` - DmPolicy string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - GroupPolicy string `json:"groupPolicy"` - GroupAllowFrom []string `json:"groupAllowFrom"` - Installed bool `json:"installed"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + GroupPolicy string `json:"groupPolicy"` + GroupAllowFrom []string `json:"groupAllowFrom"` + Bots []AgentDingTalkBot `json:"bots"` + Installed bool `json:"installed"` } type AgentWeixinLoginReq struct { @@ -358,28 +354,26 @@ type AgentWeixinLoginReq struct { } type AgentQQBotConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - Enabled bool `json:"enabled"` - AppID string `json:"appId" validate:"required"` - ClientSecret string `json:"clientSecret" validate:"required"` + AgentID uint `json:"agentId" validate:"required"` + Enabled bool `json:"enabled"` + Bots []AgentQQBotBot `json:"bots" validate:"required,min=1"` } type AgentQQBotConfig struct { - Enabled bool `json:"enabled"` - AppID string `json:"appId"` - ClientSecret string `json:"clientSecret"` - Installed bool `json:"installed"` + Enabled bool `json:"enabled"` + Bots []AgentQQBotBot `json:"bots"` + Installed bool `json:"installed"` } type AgentPluginInstallReq struct { AgentID uint `json:"agentId" validate:"required"` - Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"` + Type string `json:"type" validate:"required,oneof=feishu qqbot wecom dingtalk weixin"` TaskID string `json:"taskID" validate:"required"` } type AgentPluginCheckReq struct { AgentID uint `json:"agentId" validate:"required"` - Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"` + Type string `json:"type" validate:"required,oneof=feishu qqbot wecom dingtalk weixin"` } type AgentPluginStatus struct { @@ -387,20 +381,57 @@ type AgentPluginStatus struct { } type AgentDiscordConfigUpdateReq struct { - AgentID uint `json:"agentId" validate:"required"` - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy" validate:"required"` - GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"` - Token string `json:"token" validate:"required"` - Proxy string `json:"proxy"` + AgentID uint `json:"agentId" validate:"required"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy" validate:"required"` + GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"` + Proxy string `json:"proxy"` + DefaultAccount string `json:"defaultAccount" validate:"required"` + Bots []AgentDiscordBot `json:"bots" validate:"required,min=1"` } type AgentDiscordConfig struct { - Enabled bool `json:"enabled"` - DmPolicy string `json:"dmPolicy"` - GroupPolicy string `json:"groupPolicy"` - Token string `json:"token"` - Proxy string `json:"proxy"` + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy"` + GroupPolicy string `json:"groupPolicy"` + Proxy string `json:"proxy"` + DefaultAccount string `json:"defaultAccount"` + Bots []AgentDiscordBot `json:"bots"` +} + +type AgentChannelBotBase struct { + AccountID string `json:"accountId"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + IsDefault bool `json:"isDefault"` +} + +type AgentFeishuBot struct { + AgentChannelBotBase + AppID string `json:"appId"` + AppSecret string `json:"appSecret"` +} + +type AgentTelegramBot struct { + AgentChannelBotBase + BotToken string `json:"botToken"` +} + +type AgentDiscordBot struct { + AgentChannelBotBase + Token string `json:"token"` +} + +type AgentQQBotBot struct { + AgentChannelBotBase + AppID string `json:"appId"` + ClientSecret string `json:"clientSecret"` +} + +type AgentDingTalkBot struct { + AgentChannelBotBase + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` } type AgentSecurityConfigUpdateReq struct { diff --git a/agent/app/service/agents_channels.go b/agent/app/service/agents_channels.go index 1b2f15ffa66f..ba24317bbee3 100644 --- a/agent/app/service/agents_channels.go +++ b/agent/app/service/agents_channels.go @@ -3,6 +3,7 @@ package service import ( "fmt" "path" + "sort" "strings" "time" @@ -15,24 +16,24 @@ import ( ) func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) { - _, _, conf, err := a.loadAgentConfig(req.AgentID) + _, install, conf, err := a.loadAgentConfig(req.AgentID) if err != nil { return nil, err } result := extractFeishuConfig(conf) + installed, _ := checkPluginInstalled(install.ContainerName, "feishu") + result.Installed = installed return &result, nil } func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error { return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { - setFeishuConfig(conf, dto.AgentFeishuConfig{ - Enabled: req.Enabled, - DmPolicy: req.DmPolicy, - BotName: req.BotName, - AppID: req.AppID, - AppSecret: req.AppSecret, - }) - setFeishuPluginEnabled(conf, req.Enabled) + config := dto.AgentFeishuConfig{ + Enabled: req.Enabled, + Bots: req.Bots, + } + setFeishuConfig(conf, config) + setFeishuPluginEnabled(conf, config.Enabled && hasEnabledFeishuBots(config.Bots)) return nil }) } @@ -49,10 +50,11 @@ func (a AgentService) GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.Ag func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error { 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, - BotToken: req.BotToken, - Proxy: req.Proxy, + Enabled: req.Enabled, + DmPolicy: req.DmPolicy, + Proxy: req.Proxy, + DefaultAccount: req.DefaultAccount, + Bots: req.Bots, }) return nil }) @@ -70,11 +72,12 @@ func (a AgentService) GetDiscordConfig(req dto.AgentIDReq) (*dto.AgentDiscordCon func (a AgentService) UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error { 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, - GroupPolicy: req.GroupPolicy, - Token: req.Token, - Proxy: req.Proxy, + Enabled: req.Enabled, + DmPolicy: req.DmPolicy, + GroupPolicy: req.GroupPolicy, + Proxy: req.Proxy, + DefaultAccount: req.DefaultAccount, + Bots: req.Bots, }) return nil }) @@ -94,9 +97,8 @@ func (a AgentService) GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig, func (a AgentService) UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error { return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setQQBotConfig(conf, dto.AgentQQBotConfig{ - Enabled: req.Enabled, - AppID: req.AppID, - ClientSecret: req.ClientSecret, + Enabled: req.Enabled, + Bots: req.Bots, }) return nil }) @@ -140,12 +142,11 @@ func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error { setDingTalkConfig(conf, dto.AgentDingTalkConfig{ Enabled: req.Enabled, - ClientID: req.ClientID, - ClientSecret: req.ClientSecret, DmPolicy: req.DmPolicy, AllowFrom: req.AllowFrom, GroupPolicy: req.GroupPolicy, GroupAllowFrom: req.GroupAllowFrom, + Bots: req.Bots, }) return nil }) @@ -230,192 +231,277 @@ func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveRe if err != nil { return err } - if err := cmd.RunDefaultBashCf( + if req.AccountID != "" { + return cmd.RunDefaultBashCf( + "docker exec %s openclaw pairing approve %s %q --account %q", + install.ContainerName, + req.Type, + req.PairingCode, + req.AccountID, + ) + } + return cmd.RunDefaultBashCf( "docker exec %s openclaw pairing approve %s %q", install.ContainerName, req.Type, - strings.TrimSpace(req.PairingCode), - ); err != nil { - return err - } - return nil + req.PairingCode, + ) } func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig { - result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"} - channels, ok := conf["channels"].(map[string]interface{}) - if !ok { - return result - } - feishu, ok := channels["feishu"].(map[string]interface{}) - if !ok { + result := dto.AgentFeishuConfig{Enabled: true, Bots: []dto.AgentFeishuBot{defaultFeishuBot()}} + feishu := getChannelConfig(conf, "feishu") + if len(feishu) == 0 { return result } if enabled, ok := feishu["enabled"].(bool); ok { result.Enabled = enabled } - if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" { - result.DmPolicy = dmPolicy - } - accounts, ok := feishu["accounts"].(map[string]interface{}) - if !ok { - return result - } - main, ok := accounts["main"].(map[string]interface{}) - if !ok { - return result - } - if appID, ok := main["appId"].(string); ok { - result.AppID = appID - } - if appSecret, ok := main["appSecret"].(string); ok { - result.AppSecret = appSecret - } - if botName, ok := main["botName"].(string); ok { - result.BotName = botName + defaultBot := defaultFeishuBot() + defaultBot.Enabled = extractBoolValue(feishu["enabled"], true) + defaultBot.Name = extractDisplayName(feishu, extractStringValue(feishu["botName"]), "Default") + defaultBot.AppID = extractStringValue(feishu["appId"]) + defaultBot.AppSecret = extractStringValue(feishu["appSecret"]) + bots := []dto.AgentFeishuBot{defaultBot} + accounts := childMap(feishu, "accounts") + for _, accountID := range sortedChildKeys(accounts) { + account := childMap(accounts, accountID) + bots = append(bots, dto.AgentFeishuBot{ + AgentChannelBotBase: dto.AgentChannelBotBase{ + AccountID: accountID, + Name: extractDisplayName(account, extractStringValue(account["botName"]), accountID), + Enabled: extractBoolValue(account["enabled"], true), + }, + AppID: extractStringValue(account["appId"]), + AppSecret: extractStringValue(account["appSecret"]), + }) } + result.Bots = bots return result } func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) { channels := ensureChildMap(conf, "channels") feishu := ensureChildMap(channels, "feishu") - feishu["enabled"] = config.Enabled - feishu["dmPolicy"] = config.DmPolicy - - accounts := ensureChildMap(feishu, "accounts") - main := ensureChildMap(accounts, "main") - main["appId"] = config.AppID - main["appSecret"] = config.AppSecret - main["botName"] = config.BotName - - if strings.EqualFold(config.DmPolicy, "open") { - feishu["allowFrom"] = []string{"*"} + defaultBot := getDefaultFeishuBot(config.Bots) + effectiveEnabled := config.Enabled && hasEnabledFeishuBots(config.Bots) + feishu["enabled"] = effectiveEnabled + feishu["appId"] = defaultBot.AppID + feishu["appSecret"] = defaultBot.AppSecret + feishu["botName"] = defaultBot.Name + accounts := make(map[string]interface{}, len(config.Bots)) + for _, bot := range config.Bots { + if bot.AccountID == "default" || bot.IsDefault { + continue + } + accounts[bot.AccountID] = map[string]interface{}{ + "enabled": bot.Enabled, + "botName": bot.Name, + "appId": bot.AppID, + "appSecret": bot.AppSecret, + } } + feishu["accounts"] = accounts } func setFeishuPluginEnabled(conf map[string]interface{}, enabled bool) { plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") - feishu := ensureChildMap(entries, "feishu") - feishu["enabled"] = enabled + lark := ensureChildMap(entries, "openclaw-lark") + lark["enabled"] = enabled + legacy := ensureChildMap(entries, "feishu") + legacy["enabled"] = false } func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig { result := dto.AgentTelegramConfig{Enabled: true, DmPolicy: "pairing"} - channels, ok := conf["channels"].(map[string]interface{}) - if !ok { - return result - } - telegram, ok := channels["telegram"].(map[string]interface{}) - if !ok { + telegram := getChannelConfig(conf, "telegram") + if len(telegram) == 0 { return result } if enabled, ok := telegram["enabled"].(bool); ok { result.Enabled = enabled } - if dmPolicy, ok := telegram["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" { + if dmPolicy := extractStringValue(telegram["dmPolicy"]); dmPolicy != "" { result.DmPolicy = dmPolicy } - if botToken, ok := telegram["botToken"].(string); ok { - result.BotToken = botToken + result.Proxy = extractStringValue(telegram["proxy"]) + 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, + } + } } - if proxy, ok := telegram["proxy"].(string); ok { - result.Proxy = proxy + 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"]), + }) } + 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 := map[string]interface{}{ - "enabled": config.Enabled, - "dmPolicy": config.DmPolicy, - "botToken": config.BotToken, - } - if strings.EqualFold(config.DmPolicy, "open") { + telegram := ensureChildMap(channels, "telegram") + defaultAccount := normalizeDefaultAccount(config.DefaultAccount, getTelegramBotAccountIDs(config.Bots)) + effectiveEnabled := config.Enabled && hasEnabledTelegramBots(config.Bots) + telegram["enabled"] = effectiveEnabled + telegram["dmPolicy"] = config.DmPolicy + telegram["defaultAccount"] = defaultAccount + if config.DmPolicy == "open" { telegram["allowFrom"] = []string{"*"} + } else { + delete(telegram, "allowFrom") } - if strings.TrimSpace(config.Proxy) != "" { - telegram["proxy"] = strings.TrimSpace(config.Proxy) + if config.Proxy != "" { + telegram["proxy"] = config.Proxy + } else { + delete(telegram, "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, + "botToken": bot.BotToken, + } } - channels["telegram"] = telegram + telegram["accounts"] = accounts + delete(telegram, "botToken") } func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig { result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", GroupPolicy: "open"} - channels, ok := conf["channels"].(map[string]interface{}) - if !ok { - return result - } - discord, ok := channels["discord"].(map[string]interface{}) - if !ok { + discord := getChannelConfig(conf, "discord") + if len(discord) == 0 { return result } if enabled, ok := discord["enabled"].(bool); ok { result.Enabled = enabled } - if token, ok := discord["token"].(string); ok { - result.Token = token + 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, ok := discord["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" { + if groupPolicy := extractStringValue(discord["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } - if proxy, ok := discord["proxy"].(string); ok { - result.Proxy = proxy - } - if policy, ok := discord["dmPolicy"].(string); ok && strings.TrimSpace(policy) != "" { - result.DmPolicy = policy - return result - } - dm, ok := discord["dm"].(map[string]interface{}) - if ok { - if policy, ok := dm["policy"].(string); ok && strings.TrimSpace(policy) != "" { - result.DmPolicy = policy + 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") - discord["enabled"] = config.Enabled - discord["token"] = config.Token + defaultAccount := normalizeDefaultAccount(config.DefaultAccount, getDiscordBotAccountIDs(config.Bots)) + effectiveEnabled := config.Enabled && hasEnabledDiscordBots(config.Bots) + discord["enabled"] = effectiveEnabled discord["dmPolicy"] = config.DmPolicy discord["groupPolicy"] = config.GroupPolicy - if strings.EqualFold(config.DmPolicy, "open") { + discord["defaultAccount"] = defaultAccount + if config.DmPolicy == "open" { discord["allowFrom"] = []string{"*"} } else { delete(discord, "allowFrom") } - if strings.TrimSpace(config.Proxy) != "" { - discord["proxy"] = strings.TrimSpace(config.Proxy) + 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, + } + } + discord["accounts"] = accounts + delete(discord, "token") delete(discord, "dm") } func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig { result := dto.AgentQQBotConfig{Enabled: true} - channels, ok := conf["channels"].(map[string]interface{}) - if !ok { - return result - } - qqbot, ok := channels["qqbot"].(map[string]interface{}) - if !ok { + qqbot := getChannelConfig(conf, "qqbot") + if len(qqbot) == 0 { + result.Bots = []dto.AgentQQBotBot{defaultQQBot()} return result } if enabled, ok := qqbot["enabled"].(bool); ok { result.Enabled = enabled } - if appID, ok := qqbot["appId"].(string); ok { - result.AppID = appID - } - if clientSecret, ok := qqbot["clientSecret"].(string); ok { - result.ClientSecret = clientSecret + bots := []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"]), + }, + } + if bots[0].Name == "" { + bots[0].Name = "Default" + } + for _, accountID := range sortedChildKeys(childMap(qqbot, "accounts")) { + account := childMap(childMap(qqbot, "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"]), + }) } + result.Bots = bots return result } @@ -432,51 +518,67 @@ func extractWecomConfig(conf map[string]interface{}) dto.AgentWecomConfig { if enabled, ok := wecom["enabled"].(bool); ok { result.Enabled = enabled } - if dmPolicy, ok := wecom["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" { - result.DmPolicy = strings.TrimSpace(dmPolicy) - } - if botID, ok := wecom["botId"].(string); ok { - result.BotID = botID - } - if secret, ok := wecom["secret"].(string); ok { - result.Secret = secret + if dmPolicy := extractStringValue(wecom["dmPolicy"]); dmPolicy != "" { + result.DmPolicy = dmPolicy } + 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: "pairing", + DmPolicy: "open", GroupPolicy: "disabled", AllowFrom: []string{}, GroupAllowFrom: []string{}, } - channels, ok := conf["channels"].(map[string]interface{}) - if !ok { - return result - } - dingtalk, ok := channels["dingtalk-connector"].(map[string]interface{}) - if !ok { + dingtalk := getChannelConfig(conf, "dingtalk-connector") + if len(dingtalk) == 0 { return result } if enabled, ok := dingtalk["enabled"].(bool); ok { result.Enabled = enabled } - if clientID, ok := dingtalk["clientId"].(string); ok { - result.ClientID = clientID - } - if clientSecret, ok := dingtalk["clientSecret"].(string); ok { - result.ClientSecret = clientSecret - } - if dmPolicy, ok := dingtalk["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" { - result.DmPolicy = dmPolicy + if dmPolicy := extractStringValue(dingtalk["dmPolicy"]); dmPolicy != "" { + if dmPolicy == "pairing" { + result.DmPolicy = "open" + } else { + result.DmPolicy = dmPolicy + } } - if groupPolicy, ok := dingtalk["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" { + if groupPolicy := extractStringValue(dingtalk["groupPolicy"]); groupPolicy != "" { result.GroupPolicy = groupPolicy } result.AllowFrom = extractStringList(dingtalk["allowFrom"]) result.GroupAllowFrom = extractStringList(dingtalk["groupAllowFrom"]) + 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: extractDisplayName(account, accountID, accountID), + Enabled: extractBoolValue(account["enabled"], true), + }, + ClientID: extractStringValue(account["clientId"]), + ClientSecret: extractStringValue(account["clientSecret"]), + }) + } + result.Bots = bots return result } @@ -484,10 +586,10 @@ func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) { channels := ensureChildMap(conf, "channels") wecom := ensureChildMap(channels, "wecom") wecom["enabled"] = config.Enabled - wecom["botId"] = strings.TrimSpace(config.BotID) - wecom["secret"] = strings.TrimSpace(config.Secret) - wecom["dmPolicy"] = strings.TrimSpace(config.DmPolicy) - if strings.EqualFold(config.DmPolicy, "open") { + wecom["botId"] = config.BotID + wecom["secret"] = config.Secret + wecom["dmPolicy"] = config.DmPolicy + if config.DmPolicy == "open" { wecom["allowFrom"] = []string{"*"} } else { wecom["allowFrom"] = []string{} @@ -502,9 +604,8 @@ func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) { func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConfig) { channels := ensureChildMap(conf, "channels") dingtalk := ensureChildMap(channels, "dingtalk-connector") - dingtalk["enabled"] = config.Enabled - dingtalk["clientId"] = strings.TrimSpace(config.ClientID) - dingtalk["clientSecret"] = strings.TrimSpace(config.ClientSecret) + effectiveEnabled := config.Enabled && hasEnabledDingTalkBots(config.Bots) + dingtalk["enabled"] = effectiveEnabled dingtalk["dmPolicy"] = config.DmPolicy dingtalk["groupPolicy"] = config.GroupPolicy dingtalk["gatewayToken"] = extractGatewayToken(conf) @@ -524,11 +625,23 @@ func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConf 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, + "name": bot.Name, + "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"] = config.Enabled + dingtalkEntry["enabled"] = effectiveEnabled gateway := ensureChildMap(conf, "gateway") httpMap := ensureChildMap(gateway, "http") @@ -540,17 +653,34 @@ func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConf func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) { channels := ensureChildMap(conf, "channels") qqbot := ensureChildMap(channels, "qqbot") + defaultBot := getDefaultQQBot(config.Bots) + effectiveEnabled := config.Enabled && hasEnabledQQBots(config.Bots) delete(qqbot, "dmPolicy") - qqbot["enabled"] = config.Enabled + qqbot["enabled"] = effectiveEnabled qqbot["allowFrom"] = []string{"*"} - qqbot["appId"] = strings.TrimSpace(config.AppID) - qqbot["clientSecret"] = strings.TrimSpace(config.ClientSecret) + qqbot["appId"] = defaultBot.AppID + qqbot["clientSecret"] = defaultBot.ClientSecret + qqbot["name"] = defaultBot.Name + + accounts := make(map[string]interface{}, len(config.Bots)) + for _, bot := range config.Bots { + if bot.AccountID == "default" || bot.IsDefault { + continue + } + accounts[bot.AccountID] = map[string]interface{}{ + "enabled": bot.Enabled, + "name": bot.Name, + "appId": bot.AppID, + "clientSecret": bot.ClientSecret, + } + } + qqbot["accounts"] = accounts plugins := ensureChildMap(conf, "plugins") entries := ensureChildMap(plugins, "entries") delete(entries, "qqbot") qqbotEntry := ensureChildMap(entries, "openclaw-qqbot") - qqbotEntry["enabled"] = config.Enabled + qqbotEntry["enabled"] = effectiveEnabled } func appendPluginAllow(conf map[string]interface{}, pluginID string) { @@ -590,7 +720,9 @@ func buildOpenclawPluginInstallScript(spec, pluginID string) string { func resolvePluginMeta(pluginType string) (string, string, error) { switch pluginType { case "qqbot": - return "@tencent-connect/openclaw-qqbot@latest", "openclaw-qqbot", nil + 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": @@ -617,3 +749,210 @@ func checkPluginInstalled(containerName, pluginType string) (bool, error) { } return true, nil } + +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 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 + } +} + +func hasEnabledFeishuBots(bots []dto.AgentFeishuBot) bool { + for _, bot := range bots { + if bot.Enabled { + return true + } + } + return false +} + +func hasEnabledTelegramBots(bots []dto.AgentTelegramBot) bool { + for _, bot := range bots { + if bot.Enabled { + return true + } + } + return false +} + +func hasEnabledDiscordBots(bots []dto.AgentDiscordBot) bool { + for _, bot := range bots { + if bot.Enabled { + return true + } + } + return false +} + +func hasEnabledQQBots(bots []dto.AgentQQBotBot) bool { + for _, bot := range bots { + if bot.Enabled { + return true + } + } + return false +} + +func hasEnabledDingTalkBots(bots []dto.AgentDingTalkBot) bool { + for _, bot := range bots { + if bot.Enabled { + 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, + }, + } +} + +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() +} diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index d45f8666c6fb..43857b787f3b 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -497,21 +497,28 @@ export namespace AI { agentId: number; } - export interface AgentFeishuConfig { + export interface AgentChannelBotBase { + accountId: string; + name: string; enabled: boolean; - dmPolicy: string; - botName: string; + isDefault: boolean; + } + + export interface AgentFeishuBot extends AgentChannelBotBase { appId: string; appSecret: string; } + export interface AgentFeishuConfig { + enabled: boolean; + bots: AgentFeishuBot[]; + installed: boolean; + } + export interface AgentFeishuConfigUpdateReq { agentId: number; enabled: boolean; - dmPolicy: string; - botName: string; - appId: string; - appSecret: string; + bots: AgentFeishuBot[]; } export interface AgentFeishuPairingApproveReq { @@ -526,22 +533,29 @@ export namespace AI { export interface AgentTelegramConfig { enabled: boolean; dmPolicy: string; - botToken: string; proxy: string; + defaultAccount: string; + bots: AgentTelegramBot[]; } export interface AgentTelegramConfigUpdateReq { agentId: number; enabled: boolean; dmPolicy: string; - botToken: string; proxy: string; + defaultAccount: string; + bots: AgentTelegramBot[]; } export interface AgentChannelPairingApproveReq { agentId: number; - type: 'feishu' | 'telegram' | 'discord' | 'wecom' | 'dingtalk-connector'; + type: 'feishu' | 'telegram' | 'discord' | 'wecom'; pairingCode: string; + accountId?: string; + } + + export interface AgentTelegramBot extends AgentChannelBotBase { + botToken: string; } export interface AgentWecomConfigReq { @@ -570,24 +584,22 @@ export namespace AI { export interface AgentDingTalkConfig { enabled: boolean; - clientId: string; - clientSecret: string; - dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled'; + dmPolicy: 'allowlist' | 'open' | 'disabled'; allowFrom: string[]; groupPolicy: 'open' | 'allowlist' | 'disabled'; groupAllowFrom: string[]; + bots: AgentDingTalkBot[]; installed: boolean; } export interface AgentDingTalkConfigUpdateReq { agentId: number; enabled: boolean; - clientId: string; - clientSecret: string; - dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled'; + dmPolicy: 'allowlist' | 'open' | 'disabled'; allowFrom: string[]; groupPolicy: 'open' | 'allowlist' | 'disabled'; groupAllowFrom: string[]; + bots: AgentDingTalkBot[]; } export interface AgentWeixinLoginReq { @@ -601,27 +613,25 @@ export namespace AI { export interface AgentQQBotConfig { enabled: boolean; - appId: string; - clientSecret: string; + bots: AgentQQBotBot[]; installed: boolean; } export interface AgentQQBotConfigUpdateReq { agentId: number; enabled: boolean; - appId: string; - clientSecret: string; + bots: AgentQQBotBot[]; } export interface AgentPluginInstallReq { agentId: number; - type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin'; + type: 'feishu' | 'qqbot' | 'wecom' | 'dingtalk' | 'weixin'; taskID: string; } export interface AgentPluginCheckReq { agentId: number; - type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin'; + type: 'feishu' | 'qqbot' | 'wecom' | 'dingtalk' | 'weixin'; } export interface AgentPluginStatus { @@ -636,8 +646,9 @@ export namespace AI { enabled: boolean; dmPolicy: string; groupPolicy: string; - token: string; proxy: string; + defaultAccount: string; + bots: AgentDiscordBot[]; } export interface AgentDiscordConfigUpdateReq { @@ -645,8 +656,23 @@ export namespace AI { enabled: boolean; dmPolicy: string; groupPolicy: string; - token: string; proxy: string; + defaultAccount: string; + bots: AgentDiscordBot[]; + } + + export interface AgentDiscordBot extends AgentChannelBotBase { + token: string; + } + + export interface AgentQQBotBot extends AgentChannelBotBase { + appId: string; + clientSecret: string; + } + + export interface AgentDingTalkBot extends AgentChannelBotBase { + clientId: string; + clientSecret: string; } export interface AgentSecurityConfigReq { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d0cbf9983224..d4ed0c132a2b 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -771,12 +771,16 @@ const message = { pluginNotInstalled: 'Plugin is not installed. Please install it first.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index d4816dc42030..1174719c1b29 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -779,12 +779,16 @@ const message = { pluginNotInstalled: 'El plugin no está instalado. Instálalo primero.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 9a0d3da8c996..9da886a1ec83 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -772,12 +772,16 @@ const message = { pluginNotInstalled: 'プラグインがインストールされていません。先にインストールしてください。', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index c3377a15a62e..4c9a9e402d1d 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -764,12 +764,16 @@ const message = { pluginNotInstalled: '플러그인이 설치되지 않았습니다. 먼저 설치해 주세요.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index e485c3cb52d1..dd678ab7dbab 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -779,12 +779,16 @@ const message = { pluginNotInstalled: 'Plugin belum dipasang. Sila pasang dahulu.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index ff3b015fbdf6..0f1d057938be 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -774,12 +774,16 @@ const message = { pluginNotInstalled: 'O plugin não está instalado. Instale-o primeiro.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 955dddc3cf18..b31c412170f5 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -771,12 +771,16 @@ const message = { pluginNotInstalled: 'Плагин не установлен. Сначала установите его.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 16b62707bb91..173bb2ea0e32 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -775,12 +775,16 @@ const message = { pluginNotInstalled: 'Eklenti yüklü değil. Lütfen önce yükleyin.', dmPolicy: 'DM Policy', groupPolicy: 'Group Policy', + policyAllowlist: 'Allowlist', policyOpen: 'Open', policyDisabled: 'Disabled', - botName: 'Bot Name', + bots: 'Bots', + addBot: 'Add Bot', + accountId: 'Account ID', + setDefaultBot: 'Set as Default', + botDuplicateAccountId: 'Account ID already exists', + botRequired: 'Add at least one bot', botId: 'Bot ID', - appId: 'App ID', - appSecret: 'App Secret', allowFrom: 'DM Allowlist', allowFromHelper: 'One sender ID per line. Used only when DM Policy is Allowlist.', allowFromPlaceholder: 'One sender ID per line', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 099df9031f27..4541f88c48b2 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -725,12 +725,16 @@ const message = { pluginNotInstalled: '插件未安裝,請先安裝插件', dmPolicy: '私聊策略', groupPolicy: '群組策略', + policyAllowlist: '白名單', policyOpen: '開放', policyDisabled: '禁用', - botName: '機器人名稱', + bots: 'Bot 列表', + addBot: '新增 Bot', + accountId: '帳戶 ID', + setDefaultBot: '設為預設 Bot', + botDuplicateAccountId: '帳戶 ID 不能重複', + botRequired: '請至少新增一個 Bot', botId: 'Bot ID', - appId: '應用 App ID', - appSecret: '應用 App Secret', allowFrom: '私聊白名單', allowFromHelper: '一行一個發送方識別碼,僅在私聊策略為白名單時生效', allowFromPlaceholder: '一行一個發送方識別碼', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index cbd8d95debdb..1f55887f3b4c 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -720,12 +720,16 @@ const message = { pluginNotInstalled: '插件未安装,请先安装插件', dmPolicy: '私聊策略', groupPolicy: '群组策略', + policyAllowlist: '白名单', policyOpen: '开放', policyDisabled: '禁用', - botName: '机器人名称', + bots: 'Bot 列表', + addBot: '新增 Bot', + accountId: '账户 ID', + setDefaultBot: '设为默认 Bot', + botDuplicateAccountId: '账户 ID 不能重复', + botRequired: '请至少添加一个 Bot', botId: 'Bot ID', - appId: '应用 App ID', - appSecret: '应用 App Secret', allowFrom: '私聊白名单', allowFromHelper: '一行一个发送方标识,仅在私聊策略为白名单时生效', allowFromPlaceholder: '一行一个发送方标识', diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels/components/channel-bots.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels/components/channel-bots.vue new file mode 100644 index 000000000000..bc261fcbc880 --- /dev/null +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/components/channel-bots.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels/dingtalk.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels/dingtalk.vue index 07d6fa374fd8..536c4c1ae55b 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/channels/dingtalk.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/dingtalk.vue @@ -5,16 +5,9 @@ - - - - - - - - + @@ -31,7 +24,7 @@ - + @@ -48,22 +41,21 @@ /> {{ t('aiTools.agents.groupAllowFromHelper') }} - + + {{ t('commons.button.save') }} - - - - - - - - - {{ t('aiTools.agents.approvePairing') }} - - @@ -73,7 +65,7 @@ import { computed, reactive, ref } from 'vue'; import type { FormInstance, FormRules } from 'element-plus'; import { useI18n } from 'vue-i18n'; import { AI } from '@/api/interface/ai'; -import { approveAgentChannelPairing, getAgentDingTalkConfig, updateAgentDingTalkConfig } from '@/api/modules/ai'; +import { getAgentDingTalkConfig, updateAgentDingTalkConfig } from '@/api/modules/ai'; import { MsgSuccess, MsgWarning } from '@/utils/message'; import { Rules } from '@/global/form-rules'; import { isOpenclawCurrentHTTPVersion } from '@/utils/agent'; @@ -81,8 +73,9 @@ import TaskLog from '@/components/log/task/index.vue'; import PluginInstall from './components/plugin-install.vue'; import VersionSupport from '../components/version-support.vue'; import { useAgentPluginChannel } from './useAgentPluginChannel'; +import ChannelBots from './components/channel-bots.vue'; -interface DingTalkForm extends Omit { +interface DingTalkForm extends Omit { allowFromText: string; groupAllowFromText: string; } @@ -94,21 +87,17 @@ const props = defineProps<{ const { t } = useI18n(); const saving = ref(false); -const approving = ref(false); -const pairingCode = ref(''); +const agentId = ref(0); const formRef = ref(); -const { agentId, installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } = +const { installed, installing, taskLogRef, checkPluginStatus, loadPlugin, installPlugin } = useAgentPluginChannel('dingtalk'); const supported = computed(() => isOpenclawCurrentHTTPVersion(props.appVersion)); const form = reactive({ enabled: true, - clientId: '', - clientSecret: '', - dmPolicy: 'pairing', - allowFrom: [], + dmPolicy: 'open', groupPolicy: 'disabled', - groupAllowFrom: [], + bots: [], allowFromText: '', groupAllowFromText: '', }); @@ -149,28 +138,45 @@ const validateGroupAllowFrom = (_rule: any, value: string, callback: (error?: Er }; const rules = reactive({ - clientId: [Rules.requiredInput], - clientSecret: [Rules.requiredInput], dmPolicy: [Rules.requiredSelect], groupPolicy: [Rules.requiredSelect], allowFromText: [{ validator: validateAllowFrom, trigger: 'blur' }], groupAllowFromText: [{ validator: validateGroupAllowFrom, trigger: 'blur' }], }); +const botFields = [ + { prop: 'clientId', label: 'Client ID', required: true }, + { prop: 'clientSecret', label: 'Client Secret', type: 'password', required: true }, +]; + +const createBot = (): AI.AgentDingTalkBot => ({ + accountId: '', + name: '', + enabled: true, + isDefault: false, + clientId: '', + clientSecret: '', +}); + +const getBotSummary = (bot: AI.AgentDingTalkBot) => { + return bot.clientId; +}; + +const updateBots = (bots: AI.AgentDingTalkBot[]) => { + form.bots = bots; +}; + const load = async (id: number) => { if (!supported.value) { return; } + agentId.value = id; await loadPlugin(id); - pairingCode.value = ''; const res = await getAgentDingTalkConfig({ agentId: id }); form.enabled = res.data?.enabled ?? true; - form.clientId = res.data?.clientId || ''; - form.clientSecret = res.data?.clientSecret || ''; - form.dmPolicy = res.data?.dmPolicy || 'pairing'; - form.allowFrom = res.data?.allowFrom || []; + form.dmPolicy = res.data?.dmPolicy || 'open'; form.groupPolicy = res.data?.groupPolicy || 'disabled'; - form.groupAllowFrom = res.data?.groupAllowFrom || []; + form.bots = res.data?.bots || []; form.allowFromText = (res.data?.allowFrom || []).join('\n'); form.groupAllowFromText = (res.data?.groupAllowFrom || []).join('\n'); }; @@ -179,18 +185,21 @@ const saveChannel = async () => { if (!supported.value || !agentId.value || !formRef.value) { return; } + if (form.bots.length === 0) { + MsgWarning(t('aiTools.agents.botRequired')); + return; + } await formRef.value.validate(); saving.value = true; try { await updateAgentDingTalkConfig({ agentId: agentId.value, enabled: form.enabled, - clientId: form.clientId, - clientSecret: form.clientSecret, dmPolicy: form.dmPolicy, allowFrom: parseTextList(form.allowFromText), groupPolicy: form.groupPolicy, groupAllowFrom: parseTextList(form.groupAllowFromText), + bots: form.bots, }); MsgSuccess(t('aiTools.agents.saveSuccess')); } finally { @@ -198,28 +207,6 @@ const saveChannel = async () => { } }; -const approvePairing = async () => { - if (!supported.value || !agentId.value) { - return; - } - if (!pairingCode.value) { - MsgWarning(t('aiTools.agents.pairingCodePlaceholder')); - return; - } - approving.value = true; - try { - await approveAgentChannelPairing({ - agentId: agentId.value, - type: 'dingtalk-connector', - pairingCode: pairingCode.value, - }); - MsgSuccess(t('aiTools.agents.pairingApproveSuccess')); - pairingCode.value = ''; - } finally { - approving.value = false; - } -}; - defineExpose({ load, }); diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue index a8c48f9a926a..9eca6e877560 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue @@ -1,5 +1,5 @@