From f3de09e5d7cdaa644c406766dfba8d769bf1d7a7 Mon Sep 17 00:00:00 2001 From: CityFun <31820853+zhengkunwang223@users.noreply.github.com> Date: Wed, 25 Feb 2026 21:59:46 +0800 Subject: [PATCH] feat: Add discord channel for Agent (#11950) --- agent/app/api/v2/agents.go | 41 ++++++ agent/app/dto/agents.go | 25 +++- agent/app/service/agents.go | 114 ++++++++++++++- agent/router/ro_ai.go | 2 + frontend/src/api/interface/ai.ts | 25 +++- frontend/src/api/modules/ai.ts | 8 ++ frontend/src/lang/modules/en.ts | 4 + frontend/src/lang/modules/es-es.ts | 4 + frontend/src/lang/modules/ja.ts | 4 + frontend/src/lang/modules/ko.ts | 4 + frontend/src/lang/modules/ms.ts | 4 + frontend/src/lang/modules/pt-br.ts | 4 + frontend/src/lang/modules/ru.ts | 4 + frontend/src/lang/modules/tr.ts | 4 + frontend/src/lang/modules/zh-Hant.ts | 4 + frontend/src/lang/modules/zh.ts | 4 + .../ai/agents/agent/config/tabs/channels.vue | 9 ++ .../agent/config/tabs/channels/discord.vue | 132 ++++++++++++++++++ .../agent/config/tabs/channels/feishu.vue | 3 +- .../agent/config/tabs/channels/telegram.vue | 8 +- 20 files changed, 402 insertions(+), 5 deletions(-) create mode 100644 frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue diff --git a/agent/app/api/v2/agents.go b/agent/app/api/v2/agents.go index 5a9861250..941982ac0 100644 --- a/agent/app/api/v2/agents.go +++ b/agent/app/api/v2/agents.go @@ -312,6 +312,47 @@ func (b *BaseApi) UpdateAgentTelegramConfig(c *gin.Context) { helper.Success(c) } +// @Tags AI +// @Summary Get Agent Discord channel config +// @Accept json +// @Param request body dto.AgentDiscordConfigReq true "request" +// @Success 200 {object} dto.AgentDiscordConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/agents/channel/discord/get [post] +func (b *BaseApi) GetAgentDiscordConfig(c *gin.Context) { + var req dto.AgentDiscordConfigReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := agentService.GetDiscordConfig(req) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags AI +// @Summary Update Agent Discord channel config +// @Accept json +// @Param request body dto.AgentDiscordConfigUpdateReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/agents/channel/discord/update [post] +func (b *BaseApi) UpdateAgentDiscordConfig(c *gin.Context) { + var req dto.AgentDiscordConfigUpdateReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := agentService.UpdateDiscordConfig(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + // @Tags AI // @Summary Approve Agent Feishu pairing code // @Accept json diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index b73324f84..0dad9f993 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -180,16 +180,39 @@ type AgentTelegramConfigUpdateReq struct { Enabled bool `json:"enabled"` DmPolicy string `json:"dmPolicy" validate:"required"` BotToken string `json:"botToken" validate:"required"` + Proxy string `json:"proxy"` } type AgentTelegramConfig struct { Enabled bool `json:"enabled"` DmPolicy string `json:"dmPolicy"` BotToken string `json:"botToken"` + Proxy string `json:"proxy"` } type AgentChannelPairingApproveReq struct { AgentID uint `json:"agentId" validate:"required"` - Type string `json:"type" validate:"required,oneof=feishu telegram"` + Type string `json:"type" validate:"required,oneof=feishu telegram discord"` PairingCode string `json:"pairingCode" validate:"required"` } + +type AgentDiscordConfigReq struct { + AgentID uint `json:"agentId" validate:"required"` +} + +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"` +} + +type AgentDiscordConfig struct { + Enabled bool `json:"enabled"` + DmPolicy string `json:"dmPolicy"` + GroupPolicy string `json:"groupPolicy"` + Token string `json:"token"` + Proxy string `json:"proxy"` +} diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 2f1733b5e..7ad9670e1 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -45,6 +45,8 @@ type IAgentService interface { UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error + GetDiscordConfig(req dto.AgentDiscordConfigReq) (*dto.AgentDiscordConfig, error) + UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error } @@ -602,6 +604,48 @@ func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) Enabled: req.Enabled, DmPolicy: req.DmPolicy, BotToken: req.BotToken, + Proxy: req.Proxy, + }) + if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil { + return err + } + return nil +} + +func (a AgentService) GetDiscordConfig(req dto.AgentDiscordConfigReq) (*dto.AgentDiscordConfig, error) { + agent, _, err := a.loadAgentAndInstall(req.AgentID) + if err != nil { + return nil, err + } + 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, _, err := a.loadAgentAndInstall(req.AgentID) + if err != nil { + return err + } + conf, err := readOpenclawConfig(agent.ConfigPath) + if err != nil { + return err + } + if req.DmPolicy == "" { + req.DmPolicy = "pairing" + } + if req.GroupPolicy == "" { + req.GroupPolicy = "open" + } + setDiscordConfig(conf, dto.AgentDiscordConfig{ + Enabled: req.Enabled, + DmPolicy: req.DmPolicy, + GroupPolicy: req.GroupPolicy, + Token: req.Token, + Proxy: req.Proxy, }) if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil { return err @@ -618,7 +662,7 @@ func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveRe if channelType == "" { channelType = "feishu" } - if channelType != "feishu" && channelType != "telegram" { + if channelType != "feishu" && channelType != "telegram" && channelType != "discord" { return fmt.Errorf("unsupported channel type: %s", channelType) } if err := cmd.RunDefaultBashCf( @@ -733,6 +777,9 @@ func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) }, }, } + if strings.EqualFold(config.DmPolicy, "open") { + feishu["allowFrom"] = []string{"*"} + } channels["feishu"] = feishu } @@ -762,6 +809,9 @@ func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig if botToken, ok := telegram["botToken"].(string); ok { result.BotToken = botToken } + if proxy, ok := telegram["proxy"].(string); ok { + result.Proxy = proxy + } return result } @@ -772,9 +822,71 @@ func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConf "dmPolicy": config.DmPolicy, "botToken": config.BotToken, } + if strings.EqualFold(config.DmPolicy, "open") { + telegram["allowFrom"] = []string{"*"} + } + if strings.TrimSpace(config.Proxy) != "" { + telegram["proxy"] = strings.TrimSpace(config.Proxy) + } channels["telegram"] = telegram } +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 { + return result + } + if enabled, ok := discord["enabled"].(bool); ok { + result.Enabled = enabled + } + if token, ok := discord["token"].(string); ok { + result.Token = token + } + if groupPolicy, ok := discord["groupPolicy"].(string); ok && strings.TrimSpace(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 + } + // backward compatibility: old nested style + dm, ok := discord["dm"].(map[string]interface{}) + if ok { + if policy, ok := dm["policy"].(string); ok && strings.TrimSpace(policy) != "" { + result.DmPolicy = policy + } + } + 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 + discord["dmPolicy"] = config.DmPolicy + discord["groupPolicy"] = config.GroupPolicy + if strings.EqualFold(config.DmPolicy, "open") { + discord["allowFrom"] = []string{"*"} + } else { + delete(discord, "allowFrom") + } + if strings.TrimSpace(config.Proxy) != "" { + discord["proxy"] = strings.TrimSpace(config.Proxy) + } else { + delete(discord, "proxy") + } + delete(discord, "dm") +} + func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error { agents, err := agentRepo.List(repo.WithByAccountID(account.ID)) if err != nil { diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index 01797d8ad..5e30ee5f3 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -56,6 +56,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.POST("/agents/channel/feishu/approve", baseApi.ApproveAgentFeishuPairing) aiToolsRouter.POST("/agents/channel/telegram/get", baseApi.GetAgentTelegramConfig) aiToolsRouter.POST("/agents/channel/telegram/update", baseApi.UpdateAgentTelegramConfig) + aiToolsRouter.POST("/agents/channel/discord/get", baseApi.GetAgentDiscordConfig) + aiToolsRouter.POST("/agents/channel/discord/update", baseApi.UpdateAgentDiscordConfig) aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing) } } diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index c35315c63..d3df72c64 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -413,6 +413,7 @@ export namespace AI { enabled: boolean; dmPolicy: string; botToken: string; + proxy: string; } export interface AgentTelegramConfigUpdateReq { @@ -420,11 +421,33 @@ export namespace AI { enabled: boolean; dmPolicy: string; botToken: string; + proxy: string; } export interface AgentChannelPairingApproveReq { agentId: number; - type: 'feishu' | 'telegram'; + type: 'feishu' | 'telegram' | 'discord'; pairingCode: string; } + + export interface AgentDiscordConfigReq { + agentId: number; + } + + export interface AgentDiscordConfig { + enabled: boolean; + dmPolicy: string; + groupPolicy: string; + token: string; + proxy: string; + } + + export interface AgentDiscordConfigUpdateReq { + agentId: number; + enabled: boolean; + dmPolicy: string; + groupPolicy: string; + token: string; + proxy: string; + } } diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts index 14c768040..66955ca4b 100644 --- a/frontend/src/api/modules/ai.ts +++ b/frontend/src/api/modules/ai.ts @@ -156,6 +156,14 @@ export const updateAgentTelegramConfig = (req: AI.AgentTelegramConfigUpdateReq) return http.post(`/ai/agents/channel/telegram/update`, req); }; +export const getAgentDiscordConfig = (req: AI.AgentDiscordConfigReq) => { + return http.post(`/ai/agents/channel/discord/get`, req); +}; + +export const updateAgentDiscordConfig = (req: AI.AgentDiscordConfigUpdateReq) => { + return http.post(`/ai/agents/channel/discord/update`, req); +}; + export const approveAgentChannelPairing = (req: AI.AgentChannelPairingApproveReq) => { return http.post(`/ai/agents/channel/pairing/approve`, req); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index e493edac6..ef0031d42 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -702,6 +702,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 272d4d147..8f9e440c4 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -698,6 +698,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 5369e9212..e6674a88b 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -687,6 +687,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index f4fb9db07..bc9bab9fe 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -684,6 +684,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 1a1e0bd18..efe76cecc 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -699,6 +699,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 3f623cbad..a2d3354cf 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -696,6 +696,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 17d3fd1be..121bb64ea 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -692,6 +692,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index e944776a4..5c8c3c0c3 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -706,6 +706,10 @@ const message = { channelsTab: 'Channels', feishu: 'Feishu', dmPolicy: 'DM Policy', + groupPolicy: 'Group Policy', + policyPairing: 'Pairing', + policyOpen: 'Open', + policyDisabled: 'Disabled', botName: 'Bot Name', appId: 'App ID', appSecret: 'App Secret', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index b29c3bcd7..c23e3c492 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -674,6 +674,10 @@ const message = { channelsTab: '聊天渠道', feishu: '飛書', dmPolicy: '私聊策略', + groupPolicy: '群組策略', + policyPairing: '配對', + policyOpen: '開放', + policyDisabled: '禁用', botName: '機器人名稱', appId: '應用 App ID', appSecret: '應用 App Secret', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 62b1f1ab6..73f79c1ca 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -676,6 +676,10 @@ const message = { channelsTab: '聊天渠道', feishu: '飞书', dmPolicy: '私聊策略', + groupPolicy: '群组策略', + policyPairing: '配对码', + policyOpen: '开放', + policyDisabled: '禁用', botName: '机器人名称', appId: '应用 App ID', appSecret: '应用 App Secret', diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels.vue index c48f43c5d..2bb8ee719 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/channels.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels.vue @@ -6,6 +6,9 @@ + + + @@ -14,18 +17,24 @@ import { nextTick, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FeishuTab from './channels/feishu.vue'; import TelegramTab from './channels/telegram.vue'; +import DiscordTab from './channels/discord.vue'; const { t } = useI18n(); const activeTab = ref('feishu'); const agentId = ref(0); const feishuRef = ref(); const telegramRef = ref(); +const discordRef = ref(); const loadCurrentTab = async () => { if (agentId.value <= 0) { return; } await nextTick(); + if (activeTab.value === 'discord') { + await discordRef.value?.load(agentId.value); + return; + } if (activeTab.value === 'telegram') { await telegramRef.value?.load(agentId.value); return; 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 new file mode 100644 index 000000000..077eb242c --- /dev/null +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/discord.vue @@ -0,0 +1,132 @@ + + + diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels/feishu.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels/feishu.vue index f12973a45..7eca7cafb 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/channels/feishu.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/feishu.vue @@ -10,7 +10,8 @@ - + + diff --git a/frontend/src/views/ai/agents/agent/config/tabs/channels/telegram.vue b/frontend/src/views/ai/agents/agent/config/tabs/channels/telegram.vue index d170e1108..ea3b6608d 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/channels/telegram.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/channels/telegram.vue @@ -5,12 +5,16 @@ - + + + + + {{ t('commons.button.save') }} @@ -50,6 +54,7 @@ const form = reactive({ enabled: true, dmPolicy: 'pairing', botToken: '', + proxy: '', }); const rules = reactive({ @@ -79,6 +84,7 @@ const saveChannel = async () => { enabled: form.enabled, dmPolicy: form.dmPolicy || 'pairing', botToken: form.botToken, + proxy: form.proxy, }); MsgSuccess(t('aiTools.agents.saveSuccess')); } finally {