mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
667fc85d54
add new field type to Channel struct config.channels refactor to channel_list update config version to 3 update the docs
1229 lines
33 KiB
Go
1229 lines
33 KiB
Go
package openclaw
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
)
|
|
|
|
type OpenClawConfig struct {
|
|
Auth *OpenClawAuth `json:"auth"`
|
|
Models *OpenClawModels `json:"models"`
|
|
Agents *OpenClawAgents `json:"agents"`
|
|
Tools *OpenClawTools `json:"tools"`
|
|
Channels *OpenClawChannels `json:"channels"`
|
|
Cron json.RawMessage `json:"cron"`
|
|
Hooks json.RawMessage `json:"hooks"`
|
|
Skills *OpenClawSkills `json:"skills"`
|
|
Memory json.RawMessage `json:"memory"`
|
|
Session json.RawMessage `json:"session"`
|
|
}
|
|
|
|
type OpenClawAuth struct {
|
|
Profiles json.RawMessage `json:"profiles"`
|
|
Order json.RawMessage `json:"order"`
|
|
}
|
|
|
|
type OpenClawModels struct {
|
|
Providers map[string]json.RawMessage `json:"providers"`
|
|
}
|
|
|
|
type ProviderConfig struct {
|
|
BaseUrl string `json:"baseUrl"`
|
|
Api string `json:"api"`
|
|
Models []ModelConfig `json:"models"`
|
|
ApiKey string `json:"apiKey"`
|
|
}
|
|
|
|
type OpenClawModelConfig struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Reasoning bool `json:"reasoning"`
|
|
Input []string `json:"input"`
|
|
Cost Cost `json:"cost"`
|
|
ContextWindow int `json:"contextWindow"`
|
|
MaxTokens int `json:"maxTokens"`
|
|
Api string `json:"api,omitempty"`
|
|
}
|
|
|
|
type Cost struct {
|
|
Input float64 `json:"input"`
|
|
Output float64 `json:"output"`
|
|
CacheRead float64 `json:"cacheRead"`
|
|
CacheWrite float64 `json:"cacheWrite"`
|
|
}
|
|
|
|
type OpenClawTools struct {
|
|
Profile *string `json:"profile"`
|
|
Allow []string `json:"allow"`
|
|
Deny []string `json:"deny"`
|
|
}
|
|
|
|
type OpenClawAgents struct {
|
|
Defaults *OpenClawAgentDefaults `json:"defaults"`
|
|
List []OpenClawAgentEntry `json:"list"`
|
|
}
|
|
|
|
type OpenClawAgentDefaults struct {
|
|
Model *OpenClawAgentModel `json:"model"`
|
|
Workspace *string `json:"workspace"`
|
|
Tools *OpenClawAgentTools `json:"tools"`
|
|
Identity *string `json:"identity"`
|
|
}
|
|
|
|
type OpenClawAgentModel struct {
|
|
Simple string `json:"-"`
|
|
Primary *string `json:"primary"`
|
|
Fallbacks []string `json:"fallbacks"`
|
|
}
|
|
|
|
func (m *OpenClawAgentModel) GetPrimary() string {
|
|
if m.Simple != "" {
|
|
return m.Simple
|
|
}
|
|
if m.Primary != nil {
|
|
return *m.Primary
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *OpenClawAgentModel) GetFallbacks() []string {
|
|
return m.Fallbacks
|
|
}
|
|
|
|
type OpenClawAgentEntry struct {
|
|
ID string `json:"id"`
|
|
Name *string `json:"name"`
|
|
Model *OpenClawAgentModel `json:"model"`
|
|
Tools *OpenClawAgentTools `json:"tools"`
|
|
Workspace *string `json:"workspace"`
|
|
Skills []string `json:"skills"`
|
|
Identity *string `json:"identity"`
|
|
}
|
|
|
|
type OpenClawAgentTools struct {
|
|
Profile *string `json:"profile"`
|
|
Allow []string `json:"allow"`
|
|
Deny []string `json:"deny"`
|
|
AlsoAllow []string `json:"alsoAllow"`
|
|
}
|
|
|
|
type OpenClawChannels struct {
|
|
Telegram *OpenClawTelegramConfig `json:"telegram"`
|
|
Discord *OpenClawDiscordConfig `json:"discord"`
|
|
Slack *OpenClawSlackConfig `json:"slack"`
|
|
WhatsApp *OpenClawWhatsAppConfig `json:"whatsapp"`
|
|
Signal *OpenClawSignalConfig `json:"signal"`
|
|
Matrix *OpenClawMatrixConfig `json:"matrix"`
|
|
GoogleChat *OpenClawGoogleChatConfig `json:"googlechat"`
|
|
Teams *OpenClawTeamsConfig `json:"msteams"`
|
|
IRC *OpenClawIrcConfig `json:"irc"`
|
|
Mattermost *OpenClawMattermostConfig `json:"mattermost"`
|
|
Feishu *OpenClawFeishuConfig `json:"feishu"`
|
|
IMessage *OpenClawIMessageConfig `json:"imessage"`
|
|
BlueBubbles *OpenClawBlueBubblesConfig `json:"bluebubbles"`
|
|
QQ *OpenClawQQConfig `json:"qq"`
|
|
DingTalk *OpenClawDingTalkConfig `json:"dingtalk"`
|
|
MaixCam *OpenClawMaixCamConfig `json:"maixcam"`
|
|
}
|
|
|
|
type OpenClawTelegramConfig struct {
|
|
BotToken *string `json:"botToken"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
GroupPolicy *string `json:"groupPolicy"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
Enabled *bool `json:"enabled"`
|
|
UseMarkdownV2 *bool `json:"useMarkdownV2"`
|
|
}
|
|
|
|
type OpenClawDiscordConfig struct {
|
|
Token *string `json:"token"`
|
|
Guilds json.RawMessage `json:"guilds"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
GroupPolicy *string `json:"groupPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawSlackConfig struct {
|
|
BotToken *string `json:"botToken"`
|
|
AppToken *string `json:"appToken"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
GroupPolicy *string `json:"groupPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawWhatsAppConfig struct {
|
|
AuthDir *string `json:"authDir"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
GroupPolicy *string `json:"groupPolicy"`
|
|
Enabled *bool `json:"enabled"`
|
|
BridgeURL *string `json:"bridgeUrl"`
|
|
}
|
|
|
|
type OpenClawSignalConfig struct {
|
|
HttpUrl *string `json:"httpUrl"`
|
|
HttpHost *string `json:"httpHost"`
|
|
HttpPort *int `json:"httpPort"`
|
|
Account *string `json:"account"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawMatrixConfig struct {
|
|
Homeserver *string `json:"homeserver"`
|
|
UserID *string `json:"userId"`
|
|
AccessToken *string `json:"accessToken"`
|
|
Rooms []string `json:"rooms"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawGoogleChatConfig struct {
|
|
ServiceAccountFile *string `json:"serviceAccountFile"`
|
|
WebhookPath *string `json:"webhookPath"`
|
|
BotUser *string `json:"botUser"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawTeamsConfig struct {
|
|
AppID *string `json:"appId"`
|
|
AppPassword *string `json:"appPassword"`
|
|
TenantID *string `json:"tenantId"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawIrcConfig struct {
|
|
Host *string `json:"host"`
|
|
Port *int `json:"port"`
|
|
TLS *bool `json:"tls"`
|
|
Nick *string `json:"nick"`
|
|
Password *string `json:"password"`
|
|
Channels []string `json:"channels"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawMattermostConfig struct {
|
|
BotToken *string `json:"botToken"`
|
|
BaseURL *string `json:"baseUrl"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawFeishuConfig struct {
|
|
AppID *string `json:"appId"`
|
|
AppSecret *string `json:"appSecret"`
|
|
Domain *string `json:"domain"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
Enabled *bool `json:"enabled"`
|
|
VerificationToken *string `json:"verificationToken"`
|
|
EncryptKey *string `json:"encryptKey"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
}
|
|
|
|
type OpenClawIMessageConfig struct {
|
|
CliPath *string `json:"cliPath"`
|
|
DbPath *string `json:"dbPath"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawBlueBubblesConfig struct {
|
|
ServerURL *string `json:"serverUrl"`
|
|
Password *string `json:"password"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawQQConfig struct {
|
|
AppID *string `json:"appId"`
|
|
AppSecret *string `json:"appSecret"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawDingTalkConfig struct {
|
|
AppID *string `json:"appId"`
|
|
AppSecret *string `json:"appSecret"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawMaixCamConfig struct {
|
|
Host *string `json:"host"`
|
|
Port *int `json:"port"`
|
|
DmPolicy *string `json:"dmPolicy"`
|
|
AllowFrom []string `json:"allowFrom"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type OpenClawSkills struct {
|
|
Entries map[string]json.RawMessage `json:"entries"`
|
|
Load json.RawMessage `json:"load"`
|
|
}
|
|
|
|
type OpenClawProviderConfig struct {
|
|
APIKey string `json:"api_key"`
|
|
BaseURL string `json:"base_url"`
|
|
}
|
|
|
|
func (c *OpenClawConfig) GetEnabled() bool {
|
|
return true
|
|
}
|
|
|
|
func LoadOpenClawConfig(path string) (*OpenClawConfig, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
var config OpenClawConfig
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
func LoadOpenClawConfigFromDir(dir string) (*OpenClawConfig, error) {
|
|
candidates := []string{
|
|
filepath.Join(dir, "openclaw.json"),
|
|
filepath.Join(dir, "config.json"),
|
|
}
|
|
|
|
for _, p := range candidates {
|
|
if _, err := os.Stat(p); err == nil {
|
|
return LoadOpenClawConfig(p)
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("no config file found in %s", dir)
|
|
}
|
|
|
|
func GetProviderConfig(models *OpenClawModels) map[string]OpenClawProviderConfig {
|
|
result := make(map[string]OpenClawProviderConfig)
|
|
if models == nil || models.Providers == nil {
|
|
return result
|
|
}
|
|
|
|
for name, raw := range models.Providers {
|
|
var prov OpenClawProviderConfig
|
|
if err := json.Unmarshal(raw, &prov); err != nil {
|
|
continue
|
|
}
|
|
mappedName := mapProvider(name)
|
|
result[mappedName] = prov
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func GetProviderConfigFromDir(dir string) map[string]ProviderConfig {
|
|
result := make(map[string]ProviderConfig)
|
|
p := filepath.Join(dir, "agents", "main", "agent", "models.json")
|
|
|
|
if _, err := os.Stat(p); err != nil {
|
|
return result
|
|
}
|
|
|
|
data, err := os.ReadFile(p)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
var models OpenClawModels
|
|
if err := json.Unmarshal(data, &models); err != nil {
|
|
return result
|
|
}
|
|
|
|
for name, raw := range models.Providers {
|
|
var prov ProviderConfig
|
|
if err := json.Unmarshal(raw, &prov); err != nil {
|
|
continue
|
|
}
|
|
mappedName := mapProvider(name)
|
|
result[mappedName] = prov
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (c *OpenClawConfig) IsChannelEnabled(name string) bool {
|
|
switch name {
|
|
case "telegram":
|
|
return c.Channels.Telegram == nil || c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled
|
|
case "discord":
|
|
return c.Channels.Discord == nil || c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled
|
|
case "slack":
|
|
return c.Channels.Slack == nil || c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled
|
|
case "matrix":
|
|
return c.Channels.Matrix == nil || c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled
|
|
case "whatsapp":
|
|
return c.Channels.WhatsApp == nil || c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled
|
|
case "feishu":
|
|
return c.Channels.Feishu == nil || c.Channels.Feishu.Enabled == nil || *c.Channels.Feishu.Enabled
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func GetChannelAllowFrom(ch any) []string {
|
|
switch c := ch.(type) {
|
|
case *OpenClawTelegramConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
case *OpenClawDiscordConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
case *OpenClawSlackConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
case *OpenClawMatrixConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
case *OpenClawWhatsAppConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
case *OpenClawFeishuConfig:
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.AllowFrom
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (c *OpenClawConfig) GetDefaultModel() (provider, model string) {
|
|
if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Model == nil {
|
|
return "anthropic", "claude-sonnet-4-20250514"
|
|
}
|
|
|
|
primary := c.Agents.Defaults.Model.GetPrimary()
|
|
if primary == "" {
|
|
return "anthropic", "claude-sonnet-4-20250514"
|
|
}
|
|
|
|
parts := strings.Split(primary, "/")
|
|
if len(parts) > 1 {
|
|
return mapProvider(parts[0]), parts[1]
|
|
}
|
|
|
|
return "anthropic", primary
|
|
}
|
|
|
|
func (c *OpenClawConfig) GetDefaultWorkspace() string {
|
|
if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Workspace == nil {
|
|
return ""
|
|
}
|
|
return rewriteWorkspacePath(*c.Agents.Defaults.Workspace)
|
|
}
|
|
|
|
func (c *OpenClawConfig) GetAgents() []OpenClawAgentEntry {
|
|
if c.Agents == nil {
|
|
return nil
|
|
}
|
|
return c.Agents.List
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasSkills() bool {
|
|
return c.Skills != nil && c.Skills.Entries != nil && len(c.Skills.Entries) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasMemory() bool {
|
|
return c.Memory != nil && len(c.Memory) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasCron() bool {
|
|
return c.Cron != nil && len(c.Cron) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasHooks() bool {
|
|
return c.Hooks != nil && len(c.Hooks) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasSession() bool {
|
|
return c.Session != nil && len(c.Session) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) HasAuthProfiles() bool {
|
|
return c.Auth != nil && c.Auth.Profiles != nil && len(c.Auth.Profiles) > 0
|
|
}
|
|
|
|
func (c *OpenClawConfig) ConvertToPicoClaw(sourceHome string) (*PicoClawConfig, []string, error) {
|
|
cfg := &PicoClawConfig{}
|
|
var warnings []string
|
|
|
|
provider, modelName := c.GetDefaultModel()
|
|
cfg.Agents.Defaults.Workspace = c.GetDefaultWorkspace()
|
|
cfg.Agents.Defaults.ModelName = modelName
|
|
|
|
providerConfigs := GetProviderConfigFromDir(sourceHome)
|
|
defaultAPIKey := ""
|
|
defaultBaseURL := ""
|
|
|
|
if provCfg, ok := providerConfigs[provider]; ok {
|
|
defaultAPIKey = provCfg.ApiKey
|
|
defaultBaseURL = provCfg.BaseUrl
|
|
}
|
|
|
|
cfg.ModelList = []ModelConfig{
|
|
{
|
|
ModelName: modelName,
|
|
Model: fmt.Sprintf("%s/%s", provider, modelName),
|
|
APIKey: defaultAPIKey,
|
|
APIBase: defaultBaseURL,
|
|
},
|
|
}
|
|
|
|
for provName, provCfg := range providerConfigs {
|
|
if provName == provider {
|
|
continue
|
|
}
|
|
if provCfg.ApiKey != "" {
|
|
continue
|
|
}
|
|
cfg.ModelList = append(cfg.ModelList, ModelConfig{
|
|
ModelName: fmt.Sprintf("%s", provName),
|
|
Model: fmt.Sprintf("%s/%s", provName, provName),
|
|
APIKey: provCfg.ApiKey,
|
|
APIBase: provCfg.BaseUrl,
|
|
})
|
|
}
|
|
|
|
cfg.Channels = c.convertChannels(&warnings)
|
|
|
|
agentList := c.convertAgents(&warnings)
|
|
if len(agentList) > 0 {
|
|
cfg.Agents.List = agentList
|
|
}
|
|
|
|
if c.HasSkills() {
|
|
warnings = append(
|
|
warnings,
|
|
fmt.Sprintf(
|
|
"Skills (%d entries) not automatically migrated - reinstall via picoclaw CLI",
|
|
len(c.Skills.Entries),
|
|
),
|
|
)
|
|
}
|
|
if c.HasMemory() {
|
|
warnings = append(warnings, "Memory backend config not migrated - PicoClaw uses SQLite with vector embeddings")
|
|
}
|
|
if c.HasCron() {
|
|
warnings = append(
|
|
warnings,
|
|
"Cron job scheduling not supported in PicoClaw - consider using external schedulers",
|
|
)
|
|
}
|
|
if c.HasHooks() {
|
|
warnings = append(warnings, "Webhook hooks not supported in PicoClaw - use event system instead")
|
|
}
|
|
if c.HasSession() {
|
|
warnings = append(warnings, "Session scope config differs - PicoClaw uses per-agent sessions by default")
|
|
}
|
|
if c.HasAuthProfiles() {
|
|
warnings = append(
|
|
warnings,
|
|
"Auth profiles (API keys, OAuth tokens) not migrated for security - set env vars manually",
|
|
)
|
|
}
|
|
|
|
return cfg, warnings, nil
|
|
}
|
|
|
|
type ModelConfig struct {
|
|
ModelName string `json:"model_name"`
|
|
Model string `json:"model"`
|
|
APIBase string `json:"api_base,omitempty"`
|
|
APIKey string `json:"api_key"`
|
|
Proxy string `json:"proxy,omitempty"`
|
|
}
|
|
|
|
type PicoClawConfig struct {
|
|
Agents AgentsConfig `json:"agents"`
|
|
Bindings []AgentBinding `json:"bindings,omitempty"`
|
|
Channels ChannelsConfig `json:"channels"`
|
|
ModelList []ModelConfig `json:"model_list"`
|
|
Gateway GatewayConfig `json:"gateway"`
|
|
Tools ToolsConfig `json:"tools"`
|
|
}
|
|
|
|
type AgentsConfig struct {
|
|
Defaults AgentDefaults `json:"defaults"`
|
|
List []AgentConfig `json:"list,omitempty"`
|
|
}
|
|
|
|
type AgentDefaults struct {
|
|
Workspace string `json:"workspace"`
|
|
RestrictToWorkspace bool `json:"restrict_to_workspace"`
|
|
Provider string `json:"provider"`
|
|
ModelName string `json:"model_name"`
|
|
Model string `json:"model,omitempty"`
|
|
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
|
|
ImageModel string `json:"image_model,omitempty"`
|
|
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
|
|
MaxTokens int `json:"max_tokens"`
|
|
Temperature *float64 `json:"temperature,omitempty"`
|
|
MaxToolIterations int `json:"max_tool_iterations"`
|
|
}
|
|
|
|
type AgentConfig struct {
|
|
ID string `json:"id"`
|
|
Default bool `json:"default,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Workspace string `json:"workspace,omitempty"`
|
|
Model *AgentModelConfig `json:"model,omitempty"`
|
|
Skills []string `json:"skills,omitempty"`
|
|
}
|
|
|
|
type AgentModelConfig struct {
|
|
Primary string `json:"primary,omitempty"`
|
|
Fallbacks []string `json:"fallbacks,omitempty"`
|
|
}
|
|
|
|
type AgentBinding struct {
|
|
AgentID string `json:"agent_id"`
|
|
Match BindingMatch `json:"match"`
|
|
}
|
|
|
|
type BindingMatch struct {
|
|
Channel string `json:"channel"`
|
|
AccountID string `json:"account_id,omitempty"`
|
|
Peer *PeerMatch `json:"peer,omitempty"`
|
|
GuildID string `json:"guild_id,omitempty"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
}
|
|
|
|
type PeerMatch struct {
|
|
Kind string `json:"kind"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
type ChannelsConfig struct {
|
|
WhatsApp WhatsAppConfig `json:"whatsapp"`
|
|
Telegram TelegramConfig `json:"telegram"`
|
|
Feishu FeishuConfig `json:"feishu"`
|
|
Discord DiscordConfig `json:"discord"`
|
|
MaixCam MaixCamConfig `json:"maixcam"`
|
|
QQ QQConfig `json:"qq"`
|
|
DingTalk DingTalkConfig `json:"dingtalk"`
|
|
Slack SlackConfig `json:"slack"`
|
|
Matrix MatrixConfig `json:"matrix"`
|
|
LINE LINEConfig `json:"line"`
|
|
}
|
|
|
|
type WhatsAppConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
BridgeURL string `json:"bridge_url"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type TelegramConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
Proxy string `json:"proxy"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
UseMarkdownV2 bool `json:"use_markdown_v2"`
|
|
}
|
|
|
|
type FeishuConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
AppID string `json:"app_id"`
|
|
AppSecret string `json:"app_secret"`
|
|
EncryptKey string `json:"encrypt_key"`
|
|
VerificationToken string `json:"verification_token"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type DiscordConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Token string `json:"token"`
|
|
MentionOnly bool `json:"mention_only"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type MaixCamConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type QQConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
AppID string `json:"app_id"`
|
|
AppSecret string `json:"app_secret"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type DingTalkConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
ClientID string `json:"client_id"`
|
|
ClientSecret string `json:"client_secret"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type SlackConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
BotToken string `json:"bot_token"`
|
|
AppToken string `json:"app_token"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type MatrixConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Homeserver string `json:"homeserver"`
|
|
UserID string `json:"user_id"`
|
|
AccessToken string `json:"access_token"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type LINEConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
ChannelSecret string `json:"channel_secret"`
|
|
ChannelAccessToken string `json:"channel_access_token"`
|
|
WebhookHost string `json:"webhook_host"`
|
|
WebhookPort int `json:"webhook_port"`
|
|
WebhookPath string `json:"webhook_path"`
|
|
AllowFrom []string `json:"allow_from"`
|
|
}
|
|
|
|
type GatewayConfig struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
type ToolsConfig struct {
|
|
Web WebToolsConfig `json:"web"`
|
|
Cron CronConfig `json:"cron"`
|
|
Exec ExecConfig `json:"exec"`
|
|
}
|
|
|
|
type WebToolsConfig struct {
|
|
Brave BraveConfig `json:"brave"`
|
|
Tavily TavilyConfig `json:"tavily"`
|
|
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"`
|
|
Perplexity PerplexityConfig `json:"perplexity"`
|
|
Proxy string `json:"proxy,omitempty"`
|
|
}
|
|
|
|
type BraveConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
APIKey string `json:"api_key"`
|
|
APIKeys []string `json:"api_keys"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
type TavilyConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
APIKey string `json:"api_key"`
|
|
APIKeys []string `json:"api_keys"`
|
|
BaseURL string `json:"base_url"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
type DuckDuckGoConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
type PerplexityConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
APIKey string `json:"api_key"`
|
|
APIKeys []string `json:"api_keys"`
|
|
MaxResults int `json:"max_results"`
|
|
}
|
|
|
|
type CronConfig struct {
|
|
ExecTimeoutMinutes int `json:"exec_timeout_minutes"`
|
|
}
|
|
|
|
type ExecConfig struct {
|
|
EnableDenyPatterns bool `json:"enable_deny_patterns"`
|
|
CustomDenyPatterns []string `json:"custom_deny_patterns"`
|
|
}
|
|
|
|
func (c *OpenClawConfig) convertChannels(warnings *[]string) ChannelsConfig {
|
|
channels := ChannelsConfig{}
|
|
|
|
if c.Channels == nil {
|
|
return channels
|
|
}
|
|
|
|
if c.Channels.Telegram != nil {
|
|
enabled := c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled
|
|
useMarkdownV2 := c.Channels.Telegram.UseMarkdownV2 != nil && *c.Channels.Telegram.UseMarkdownV2
|
|
channels.Telegram = TelegramConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.Telegram.AllowFrom,
|
|
UseMarkdownV2: useMarkdownV2,
|
|
}
|
|
if c.Channels.Telegram.BotToken != nil {
|
|
channels.Telegram.Token = *c.Channels.Telegram.BotToken
|
|
}
|
|
}
|
|
|
|
if c.Channels.Discord != nil {
|
|
enabled := c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled
|
|
channels.Discord = DiscordConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.Discord.AllowFrom,
|
|
}
|
|
if c.Channels.Discord.Token != nil {
|
|
channels.Discord.Token = *c.Channels.Discord.Token
|
|
}
|
|
}
|
|
|
|
if c.Channels.Slack != nil {
|
|
enabled := c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled
|
|
channels.Slack = SlackConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.Slack.AllowFrom,
|
|
}
|
|
if c.Channels.Slack.BotToken != nil {
|
|
channels.Slack.BotToken = *c.Channels.Slack.BotToken
|
|
}
|
|
if c.Channels.Slack.AppToken != nil {
|
|
channels.Slack.AppToken = *c.Channels.Slack.AppToken
|
|
}
|
|
}
|
|
|
|
if c.Channels.WhatsApp != nil {
|
|
enabled := c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled
|
|
channels.WhatsApp = WhatsAppConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.WhatsApp.AllowFrom,
|
|
}
|
|
if c.Channels.WhatsApp.BridgeURL != nil {
|
|
channels.WhatsApp.BridgeURL = *c.Channels.WhatsApp.BridgeURL
|
|
}
|
|
}
|
|
|
|
if c.Channels.Feishu != nil {
|
|
enabled := c.Channels.Feishu.Enabled == nil || *c.Channels.Feishu.Enabled
|
|
channels.Feishu = FeishuConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.Feishu.AllowFrom,
|
|
}
|
|
if c.Channels.Feishu.AppID != nil {
|
|
channels.Feishu.AppID = *c.Channels.Feishu.AppID
|
|
}
|
|
if c.Channels.Feishu.AppSecret != nil {
|
|
channels.Feishu.AppSecret = *c.Channels.Feishu.AppSecret
|
|
}
|
|
if c.Channels.Feishu.EncryptKey != nil {
|
|
channels.Feishu.EncryptKey = *c.Channels.Feishu.EncryptKey
|
|
}
|
|
if c.Channels.Feishu.VerificationToken != nil {
|
|
channels.Feishu.VerificationToken = *c.Channels.Feishu.VerificationToken
|
|
}
|
|
}
|
|
|
|
if c.Channels.QQ != nil && supportedChannels["qq"] {
|
|
channels.QQ = QQConfig{
|
|
Enabled: true,
|
|
AllowFrom: c.Channels.QQ.AllowFrom,
|
|
}
|
|
if c.Channels.QQ.AppID != nil {
|
|
channels.QQ.AppID = *c.Channels.QQ.AppID
|
|
}
|
|
if c.Channels.QQ.AppSecret != nil {
|
|
channels.QQ.AppSecret = *c.Channels.QQ.AppSecret
|
|
}
|
|
}
|
|
|
|
if c.Channels.DingTalk != nil && supportedChannels["dingtalk"] {
|
|
channels.DingTalk = DingTalkConfig{
|
|
Enabled: true,
|
|
AllowFrom: c.Channels.DingTalk.AllowFrom,
|
|
}
|
|
if c.Channels.DingTalk.AppID != nil {
|
|
channels.DingTalk.ClientID = *c.Channels.DingTalk.AppID
|
|
}
|
|
if c.Channels.DingTalk.AppSecret != nil {
|
|
channels.DingTalk.ClientSecret = *c.Channels.DingTalk.AppSecret
|
|
}
|
|
}
|
|
|
|
if c.Channels.MaixCam != nil && supportedChannels["maixcam"] {
|
|
channels.MaixCam = MaixCamConfig{
|
|
Enabled: true,
|
|
AllowFrom: c.Channels.MaixCam.AllowFrom,
|
|
}
|
|
if c.Channels.MaixCam.Host != nil {
|
|
channels.MaixCam.Host = *c.Channels.MaixCam.Host
|
|
}
|
|
if c.Channels.MaixCam.Port != nil {
|
|
channels.MaixCam.Port = *c.Channels.MaixCam.Port
|
|
}
|
|
}
|
|
|
|
if c.Channels.Matrix != nil && supportedChannels["matrix"] {
|
|
enabled := c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled
|
|
channels.Matrix = MatrixConfig{
|
|
Enabled: enabled,
|
|
AllowFrom: c.Channels.Matrix.AllowFrom,
|
|
}
|
|
if c.Channels.Matrix.Homeserver != nil {
|
|
channels.Matrix.Homeserver = *c.Channels.Matrix.Homeserver
|
|
}
|
|
if c.Channels.Matrix.UserID != nil {
|
|
channels.Matrix.UserID = *c.Channels.Matrix.UserID
|
|
}
|
|
if c.Channels.Matrix.AccessToken != nil {
|
|
channels.Matrix.AccessToken = *c.Channels.Matrix.AccessToken
|
|
}
|
|
}
|
|
|
|
if c.Channels.Signal != nil {
|
|
*warnings = append(*warnings, "Channel 'signal': No PicoClaw adapter available")
|
|
}
|
|
if c.Channels.IRC != nil {
|
|
*warnings = append(*warnings, "Channel 'irc': No PicoClaw adapter available")
|
|
}
|
|
if c.Channels.Mattermost != nil {
|
|
*warnings = append(*warnings, "Channel 'mattermost': No PicoClaw adapter available")
|
|
}
|
|
if c.Channels.IMessage != nil {
|
|
*warnings = append(*warnings, "Channel 'imessage': macOS-only channel - requires manual setup")
|
|
}
|
|
if c.Channels.BlueBubbles != nil {
|
|
*warnings = append(
|
|
*warnings,
|
|
"Channel 'bluebubbles': No PicoClaw adapter available - consider iMessage instead",
|
|
)
|
|
}
|
|
|
|
return channels
|
|
}
|
|
|
|
func (c *OpenClawConfig) convertAgents(warnings *[]string) []AgentConfig {
|
|
var agents []AgentConfig
|
|
|
|
if c.Agents == nil {
|
|
return agents
|
|
}
|
|
|
|
for _, entry := range c.Agents.List {
|
|
agentID := entry.ID
|
|
if agentID == "" {
|
|
continue
|
|
}
|
|
|
|
agentName := agentID
|
|
if entry.Name != nil {
|
|
agentName = *entry.Name
|
|
}
|
|
|
|
agentCfg := AgentConfig{
|
|
ID: agentID,
|
|
Name: agentName,
|
|
Default: len(agents) == 0,
|
|
}
|
|
|
|
if entry.Workspace != nil {
|
|
agentCfg.Workspace = rewriteWorkspacePath(*entry.Workspace)
|
|
}
|
|
|
|
if entry.Model != nil {
|
|
primary := entry.Model.GetPrimary()
|
|
if primary != "" {
|
|
agentCfg.Model = &AgentModelConfig{
|
|
Primary: primary,
|
|
Fallbacks: entry.Model.GetFallbacks(),
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(entry.Skills) > 0 {
|
|
agentCfg.Skills = entry.Skills
|
|
}
|
|
|
|
agents = append(agents, agentCfg)
|
|
}
|
|
|
|
return agents
|
|
}
|
|
|
|
func (c *PicoClawConfig) ToStandardConfig() *config.Config {
|
|
cfg := config.DefaultConfig()
|
|
|
|
cfg.Agents.Defaults.Workspace = c.Agents.Defaults.Workspace
|
|
cfg.Agents.Defaults.Provider = c.Agents.Defaults.Provider
|
|
cfg.Agents.Defaults.ModelName = c.Agents.Defaults.ModelName
|
|
cfg.Agents.Defaults.ModelFallbacks = c.Agents.Defaults.ModelFallbacks
|
|
|
|
for _, m := range c.ModelList {
|
|
mc := &config.ModelConfig{
|
|
ModelName: m.ModelName,
|
|
Model: m.Model,
|
|
APIBase: m.APIBase,
|
|
Proxy: m.Proxy,
|
|
}
|
|
if m.APIKey != "" {
|
|
mc.SetAPIKey(m.APIKey)
|
|
}
|
|
cfg.ModelList = append(cfg.ModelList, mc)
|
|
}
|
|
|
|
cfg.Channels = c.Channels.ToStandardChannels()
|
|
cfg.Gateway = c.Gateway.ToStandardGateway()
|
|
cfg.Tools = c.Tools.ToStandardTools()
|
|
|
|
cfg.Agents.List = make([]config.AgentConfig, len(c.Agents.List))
|
|
for i, a := range c.Agents.List {
|
|
cfg.Agents.List[i] = config.AgentConfig{
|
|
ID: a.ID,
|
|
Default: a.Default,
|
|
Name: a.Name,
|
|
Workspace: a.Workspace,
|
|
Skills: a.Skills,
|
|
}
|
|
if a.Model != nil {
|
|
cfg.Agents.List[i].Model = &config.AgentModelConfig{
|
|
Primary: a.Model.Primary,
|
|
Fallbacks: a.Model.Fallbacks,
|
|
}
|
|
}
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
func (c ChannelsConfig) ToStandardChannels() config.ChannelsConfig {
|
|
channels := make(config.ChannelsConfig)
|
|
|
|
setChannel(channels, "whatsapp", map[string]any{
|
|
"enabled": c.WhatsApp.Enabled,
|
|
"bridge_url": c.WhatsApp.BridgeURL,
|
|
})
|
|
|
|
setChannel(channels, "telegram", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.Telegram.Enabled,
|
|
"proxy": c.Telegram.Proxy,
|
|
}
|
|
if c.Telegram.Token != "" {
|
|
m["token"] = config.NewSecureString(c.Telegram.Token)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "feishu", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.Feishu.Enabled,
|
|
"app_id": c.Feishu.AppID,
|
|
}
|
|
if c.Feishu.AppSecret != "" {
|
|
m["app_secret"] = config.NewSecureString(c.Feishu.AppSecret)
|
|
}
|
|
if c.Feishu.EncryptKey != "" {
|
|
m["encrypt_key"] = config.NewSecureString(c.Feishu.EncryptKey)
|
|
}
|
|
if c.Feishu.VerificationToken != "" {
|
|
m["verification_token"] = config.NewSecureString(c.Feishu.VerificationToken)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "discord", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.Discord.Enabled,
|
|
"mention_only": c.Discord.MentionOnly,
|
|
}
|
|
if c.Discord.Token != "" {
|
|
m["token"] = config.NewSecureString(c.Discord.Token)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "maixcam", map[string]any{
|
|
"enabled": c.MaixCam.Enabled,
|
|
"host": c.MaixCam.Host,
|
|
"port": c.MaixCam.Port,
|
|
})
|
|
|
|
setChannel(channels, "qq", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.QQ.Enabled,
|
|
"app_id": c.QQ.AppID,
|
|
}
|
|
if c.QQ.AppSecret != "" {
|
|
m["app_secret"] = config.NewSecureString(c.QQ.AppSecret)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "dingtalk", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.DingTalk.Enabled,
|
|
"client_id": c.DingTalk.ClientID,
|
|
}
|
|
if c.DingTalk.ClientSecret != "" {
|
|
m["client_secret"] = config.NewSecureString(c.DingTalk.ClientSecret)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "slack", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.Slack.Enabled,
|
|
}
|
|
if c.Slack.BotToken != "" {
|
|
m["bot_token"] = config.NewSecureString(c.Slack.BotToken)
|
|
}
|
|
if c.Slack.AppToken != "" {
|
|
m["app_token"] = config.NewSecureString(c.Slack.AppToken)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "matrix", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.Matrix.Enabled,
|
|
"homeserver": c.Matrix.Homeserver,
|
|
"user_id": c.Matrix.UserID,
|
|
"allow_from": c.Matrix.AllowFrom,
|
|
"join_on_invite": true,
|
|
}
|
|
if c.Matrix.AccessToken != "" {
|
|
m["access_token"] = config.NewSecureString(c.Matrix.AccessToken)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
setChannel(channels, "line", func() map[string]any {
|
|
m := map[string]any{
|
|
"enabled": c.LINE.Enabled,
|
|
"webhook_host": c.LINE.WebhookHost,
|
|
"webhook_port": c.LINE.WebhookPort,
|
|
"webhook_path": c.LINE.WebhookPath,
|
|
}
|
|
if c.LINE.ChannelSecret != "" {
|
|
m["channel_secret"] = config.NewSecureString(c.LINE.ChannelSecret)
|
|
}
|
|
if c.LINE.ChannelAccessToken != "" {
|
|
m["channel_access_token"] = config.NewSecureString(c.LINE.ChannelAccessToken)
|
|
}
|
|
return m
|
|
}())
|
|
|
|
return channels
|
|
}
|
|
|
|
func setChannel(channels config.ChannelsConfig, name string, cfg any) {
|
|
data, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Wrap in "settings" for nested format
|
|
var m map[string]any
|
|
if err = json.Unmarshal(data, &m); err != nil {
|
|
return
|
|
}
|
|
settings := make(map[string]any)
|
|
for k, v := range m {
|
|
if _, exists := config.BaseFieldNames[k]; !exists {
|
|
settings[k] = v
|
|
delete(m, k)
|
|
}
|
|
}
|
|
if len(settings) > 0 {
|
|
m["settings"] = settings
|
|
}
|
|
nestedData, err := json.Marshal(m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
bc := &config.Channel{}
|
|
if err := json.Unmarshal(nestedData, bc); err != nil {
|
|
return
|
|
}
|
|
channels[name] = bc
|
|
}
|
|
|
|
func (c GatewayConfig) ToStandardGateway() config.GatewayConfig {
|
|
return config.GatewayConfig{
|
|
Host: c.Host,
|
|
Port: c.Port,
|
|
}
|
|
}
|
|
|
|
func (c ToolsConfig) ToStandardTools() config.ToolsConfig {
|
|
brave := config.BraveConfig{
|
|
Enabled: c.Web.Brave.Enabled,
|
|
MaxResults: c.Web.Brave.MaxResults,
|
|
}
|
|
if c.Web.Brave.APIKey != "" {
|
|
brave.SetAPIKey(c.Web.Brave.APIKey)
|
|
}
|
|
if len(c.Web.Brave.APIKeys) > 0 {
|
|
brave.SetAPIKeys(c.Web.Brave.APIKeys)
|
|
}
|
|
|
|
tavily := config.TavilyConfig{
|
|
Enabled: c.Web.Tavily.Enabled,
|
|
BaseURL: c.Web.Tavily.BaseURL,
|
|
MaxResults: c.Web.Tavily.MaxResults,
|
|
}
|
|
if c.Web.Tavily.APIKey != "" {
|
|
tavily.SetAPIKey(c.Web.Tavily.APIKey)
|
|
}
|
|
|
|
perplexity := config.PerplexityConfig{
|
|
Enabled: c.Web.Perplexity.Enabled,
|
|
MaxResults: c.Web.Perplexity.MaxResults,
|
|
}
|
|
if c.Web.Perplexity.APIKey != "" {
|
|
perplexity.SetAPIKey(c.Web.Perplexity.APIKey)
|
|
}
|
|
|
|
return config.ToolsConfig{
|
|
Web: config.WebToolsConfig{
|
|
Brave: brave,
|
|
Tavily: tavily,
|
|
DuckDuckGo: config.DuckDuckGoConfig{
|
|
Enabled: c.Web.DuckDuckGo.Enabled,
|
|
MaxResults: c.Web.DuckDuckGo.MaxResults,
|
|
},
|
|
Perplexity: perplexity,
|
|
Proxy: c.Web.Proxy,
|
|
},
|
|
Cron: config.CronToolsConfig{
|
|
ExecTimeoutMinutes: c.Cron.ExecTimeoutMinutes,
|
|
},
|
|
Exec: config.ExecConfig{
|
|
EnableDenyPatterns: c.Exec.EnableDenyPatterns,
|
|
CustomDenyPatterns: c.Exec.CustomDenyPatterns,
|
|
AllowRemote: config.DefaultConfig().Tools.Exec.AllowRemote,
|
|
},
|
|
}
|
|
}
|