mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
1654 lines
52 KiB
Go
1654 lines
52 KiB
Go
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()
|
|
}
|