mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
Merge branch 'main' into version
This commit is contained in:
+53
-19
@@ -259,6 +259,7 @@ type AgentDefaults struct {
|
||||
MaxMediaSize int `json:"max_media_size,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_MEDIA_SIZE"`
|
||||
Routing *RoutingConfig `json:"routing,omitempty"`
|
||||
ToolFeedback ToolFeedbackConfig `json:"tool_feedback,omitempty"`
|
||||
LogLevel string `json:"log_level,omitempty" env:"PICOCLAW_LOG_LEVEL"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -307,6 +308,7 @@ type ChannelsConfig struct {
|
||||
WeCom WeComConfig `json:"wecom"`
|
||||
WeComApp WeComAppConfig `json:"wecom_app"`
|
||||
WeComAIBot WeComAIBotConfig `json:"wecom_aibot"`
|
||||
Weixin WeixinConfig `json:"weixin"`
|
||||
Pico PicoConfig `json:"pico"`
|
||||
PicoClient PicoClientConfig `json:"pico_client"`
|
||||
IRC IRCConfig `json:"irc"`
|
||||
@@ -751,6 +753,27 @@ func (c *WeComAIBotConfig) SetSecret(secret string) {
|
||||
c.secDirty = true
|
||||
}
|
||||
|
||||
type WeixinConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_WEIXIN_ENABLED"`
|
||||
token string
|
||||
BaseURL string `json:"base_url" env:"PICOCLAW_CHANNELS_WEIXIN_BASE_URL"`
|
||||
CDNBaseURL string `json:"cdn_base_url" env:"PICOCLAW_CHANNELS_WEIXIN_CDN_BASE_URL"`
|
||||
Proxy string `json:"proxy" env:"PICOCLAW_CHANNELS_WEIXIN_PROXY"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_WEIXIN_ALLOW_FROM"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_WEIXIN_REASONING_CHANNEL_ID"`
|
||||
secDirty bool
|
||||
}
|
||||
|
||||
func (c *WeixinConfig) Token() string {
|
||||
return c.token
|
||||
}
|
||||
|
||||
func (c *WeixinConfig) SetToken(token string) *WeixinConfig {
|
||||
c.token = token
|
||||
c.secDirty = true
|
||||
return c
|
||||
}
|
||||
|
||||
type PicoConfig struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_PICO_ENABLED"`
|
||||
token string
|
||||
@@ -1391,82 +1414,87 @@ func applySecurityConfig(cfg *Config, sec *SecurityConfig) error {
|
||||
|
||||
// Handle Telegram token
|
||||
if sec.Channels.Telegram != nil && sec.Channels.Telegram.Token != "" {
|
||||
cfg.Channels.Telegram.SetToken(sec.Channels.Telegram.Token)
|
||||
cfg.Channels.Telegram.token = sec.Channels.Telegram.Token
|
||||
}
|
||||
|
||||
// Handle Feishu credentials
|
||||
if sec.Channels.Feishu != nil {
|
||||
if sec.Channels.Feishu.AppSecret != "" {
|
||||
cfg.Channels.Feishu.SetAppSecret(sec.Channels.Feishu.AppSecret)
|
||||
cfg.Channels.Feishu.appSecret = sec.Channels.Feishu.AppSecret
|
||||
}
|
||||
if sec.Channels.Feishu.EncryptKey != "" {
|
||||
cfg.Channels.Feishu.SetEncryptKey(sec.Channels.Feishu.EncryptKey)
|
||||
cfg.Channels.Feishu.encryptKey = sec.Channels.Feishu.EncryptKey
|
||||
}
|
||||
if sec.Channels.Feishu.VerificationToken != "" {
|
||||
cfg.Channels.Feishu.SetVerificationToken(sec.Channels.Feishu.VerificationToken)
|
||||
cfg.Channels.Feishu.verificationToken = sec.Channels.Feishu.VerificationToken
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Discord token
|
||||
if sec.Channels.Discord != nil && sec.Channels.Discord.Token != "" {
|
||||
cfg.Channels.Discord.SetToken(sec.Channels.Discord.Token)
|
||||
cfg.Channels.Discord.token = sec.Channels.Discord.Token
|
||||
}
|
||||
|
||||
// Handle Weixin token
|
||||
if sec.Channels.Weixin != nil && sec.Channels.Weixin.Token != "" {
|
||||
cfg.Channels.Discord.token = sec.Channels.Discord.Token
|
||||
}
|
||||
|
||||
// Handle DingTalk client secret
|
||||
if sec.Channels.DingTalk != nil && sec.Channels.DingTalk.ClientSecret != "" {
|
||||
cfg.Channels.DingTalk.SetClientSecret(sec.Channels.DingTalk.ClientSecret)
|
||||
cfg.Channels.DingTalk.clientSecret = sec.Channels.DingTalk.ClientSecret
|
||||
}
|
||||
|
||||
// Handle Slack tokens
|
||||
if sec.Channels.Slack != nil {
|
||||
if sec.Channels.Slack.BotToken != "" {
|
||||
cfg.Channels.Slack.SetBotToken(sec.Channels.Slack.BotToken)
|
||||
cfg.Channels.Slack.botToken = sec.Channels.Slack.BotToken
|
||||
}
|
||||
if sec.Channels.Slack.AppToken != "" {
|
||||
cfg.Channels.Slack.SetAppToken(sec.Channels.Slack.AppToken)
|
||||
cfg.Channels.Slack.appToken = sec.Channels.Slack.AppToken
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Matrix access token
|
||||
if sec.Channels.Matrix != nil && sec.Channels.Matrix.AccessToken != "" {
|
||||
cfg.Channels.Matrix.SetAccessToken(sec.Channels.Matrix.AccessToken)
|
||||
cfg.Channels.Matrix.accessToken = sec.Channels.Matrix.AccessToken
|
||||
}
|
||||
|
||||
// Handle LINE credentials
|
||||
if sec.Channels.LINE != nil {
|
||||
if sec.Channels.LINE.ChannelSecret != "" {
|
||||
cfg.Channels.LINE.SetChannelSecret(sec.Channels.LINE.ChannelSecret)
|
||||
cfg.Channels.LINE.channelSecret = sec.Channels.LINE.ChannelSecret
|
||||
}
|
||||
if sec.Channels.LINE.ChannelAccessToken != "" {
|
||||
cfg.Channels.LINE.SetChannelAccessToken(sec.Channels.LINE.ChannelAccessToken)
|
||||
cfg.Channels.LINE.channelAccessToken = sec.Channels.LINE.ChannelAccessToken
|
||||
}
|
||||
}
|
||||
|
||||
// Handle OneBot access token
|
||||
if sec.Channels.OneBot != nil && sec.Channels.OneBot.AccessToken != "" {
|
||||
cfg.Channels.OneBot.SetAccessToken(sec.Channels.OneBot.AccessToken)
|
||||
cfg.Channels.OneBot.accessToken = sec.Channels.OneBot.AccessToken
|
||||
}
|
||||
|
||||
// Handle WeCom token and encoding key
|
||||
if sec.Channels.WeCom != nil {
|
||||
if sec.Channels.WeCom.Token != "" {
|
||||
cfg.Channels.WeCom.SetToken(sec.Channels.WeCom.Token)
|
||||
cfg.Channels.WeCom.token = sec.Channels.WeCom.Token
|
||||
}
|
||||
if sec.Channels.WeCom.EncodingAESKey != "" {
|
||||
cfg.Channels.WeCom.SetEncodingAESKey(sec.Channels.WeCom.EncodingAESKey)
|
||||
cfg.Channels.WeCom.encodingAESKey = sec.Channels.WeCom.EncodingAESKey
|
||||
}
|
||||
}
|
||||
|
||||
// Handle WeCom App credentials
|
||||
if sec.Channels.WeComApp != nil {
|
||||
if sec.Channels.WeComApp.CorpSecret != "" {
|
||||
cfg.Channels.WeComApp.SetCorpSecret(sec.Channels.WeComApp.CorpSecret)
|
||||
cfg.Channels.WeComApp.corpSecret = sec.Channels.WeComApp.CorpSecret
|
||||
}
|
||||
if sec.Channels.WeComApp.Token != "" {
|
||||
cfg.Channels.WeComApp.SetToken(sec.Channels.WeComApp.Token)
|
||||
cfg.Channels.WeComApp.token = sec.Channels.WeComApp.Token
|
||||
}
|
||||
if sec.Channels.WeComApp.EncodingAESKey != "" {
|
||||
cfg.Channels.WeComApp.SetEncodingAESKey(sec.Channels.WeComApp.EncodingAESKey)
|
||||
cfg.Channels.WeComApp.encodingAESKey = sec.Channels.WeComApp.EncodingAESKey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1485,7 +1513,7 @@ func applySecurityConfig(cfg *Config, sec *SecurityConfig) error {
|
||||
|
||||
// Handle Pico channel token
|
||||
if sec.Channels.Pico != nil && sec.Channels.Pico.Token != "" {
|
||||
cfg.Channels.Pico.SetToken(sec.Channels.Pico.Token)
|
||||
cfg.Channels.Pico.token = sec.Channels.Pico.Token
|
||||
}
|
||||
|
||||
// Handle IRC passwords
|
||||
@@ -1503,7 +1531,7 @@ func applySecurityConfig(cfg *Config, sec *SecurityConfig) error {
|
||||
|
||||
// Handle QQ app secret
|
||||
if sec.Channels.QQ != nil && sec.Channels.QQ.AppSecret != "" {
|
||||
cfg.Channels.QQ.SetAppSecret(sec.Channels.QQ.AppSecret)
|
||||
cfg.Channels.QQ.appSecret = sec.Channels.QQ.AppSecret
|
||||
}
|
||||
|
||||
cfg.security = sec
|
||||
@@ -1649,6 +1677,12 @@ func SaveConfig(path string, cfg *Config) error {
|
||||
}
|
||||
cfg.Channels.Discord.secDirty = false
|
||||
}
|
||||
if cfg.Channels.Weixin.secDirty {
|
||||
cfg.security.Channels.Weixin = &WeixinSecurity{
|
||||
Token: cfg.Channels.Weixin.Token(),
|
||||
}
|
||||
cfg.Channels.Discord.secDirty = false
|
||||
}
|
||||
if cfg.Channels.QQ.secDirty {
|
||||
cfg.security.Channels.QQ = &QQSecurity{
|
||||
AppSecret: cfg.Channels.QQ.AppSecret(),
|
||||
|
||||
@@ -88,6 +88,7 @@ type channelsConfigV0 struct {
|
||||
Feishu feishuConfigV0 `json:"feishu"`
|
||||
Discord discordConfigV0 `json:"discord"`
|
||||
MaixCam maixcamConfigV0 `json:"maixcam"`
|
||||
Weixin weixinConfigV0 `json:"weixin"`
|
||||
QQ qqConfigV0 `json:"qq"`
|
||||
DingTalk dingtalkConfigV0 `json:"dingtalk"`
|
||||
Slack slackConfigV0 `json:"slack"`
|
||||
@@ -107,6 +108,7 @@ func (v *channelsConfigV0) ToChannelsConfig() (ChannelsConfig, ChannelsSecurity)
|
||||
discord, discordSecurity := v.Discord.ToDiscordConfig()
|
||||
maixcam := v.MaixCam.ToMaixCamConfig()
|
||||
qq, qqSecurity := v.QQ.ToQQConfig()
|
||||
weixin, weixinSecurity := v.Weixin.ToWeiXinConfig()
|
||||
dingtalk, dingtalkSecurity := v.DingTalk.ToDingTalkConfig()
|
||||
slack, slackSecurity := v.Slack.ToSlackConfig()
|
||||
matrix, matrixSecurity := v.Matrix.ToMatrixConfig()
|
||||
@@ -125,6 +127,7 @@ func (v *channelsConfigV0) ToChannelsConfig() (ChannelsConfig, ChannelsSecurity)
|
||||
Discord: discord,
|
||||
MaixCam: maixcam,
|
||||
QQ: qq,
|
||||
Weixin: weixin,
|
||||
DingTalk: dingtalk,
|
||||
Slack: slack,
|
||||
Matrix: matrix,
|
||||
@@ -140,6 +143,7 @@ func (v *channelsConfigV0) ToChannelsConfig() (ChannelsConfig, ChannelsSecurity)
|
||||
Feishu: &feishuSecurity,
|
||||
Discord: &discordSecurity,
|
||||
QQ: &qqSecurity,
|
||||
Weixin: &weixinSecurity,
|
||||
DingTalk: &dingtalkSecurity,
|
||||
Slack: &slackSecurity,
|
||||
Matrix: &matrixSecurity,
|
||||
@@ -463,6 +467,30 @@ func (v *wecomConfigV0) ToWeComConfig() (WeComConfig, WeComSecurity) {
|
||||
}
|
||||
}
|
||||
|
||||
type weixinConfigV0 struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_WEIXIN_ENABLED"`
|
||||
Token string `json:"token" env:"PICOCLAW_CHANNELS_WEIXIN_TOKEN"`
|
||||
BaseURL string `json:"base_url" env:"PICOCLAW_CHANNELS_WEIXIN_BASE_URL"`
|
||||
CDNBaseURL string `json:"cdn_base_url" env:"PICOCLAW_CHANNELS_WEIXIN_CDN_BASE_URL"`
|
||||
Proxy string `json:"proxy" env:"PICOCLAW_CHANNELS_WEIXIN_PROXY"`
|
||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_WEIXIN_ALLOW_FROM"`
|
||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_WEIXIN_REASONING_CHANNEL_ID"`
|
||||
}
|
||||
|
||||
func (v *weixinConfigV0) ToWeiXinConfig() (WeixinConfig, WeixinSecurity) {
|
||||
return WeixinConfig{
|
||||
Enabled: v.Enabled,
|
||||
token: v.Token,
|
||||
BaseURL: v.BaseURL,
|
||||
CDNBaseURL: v.CDNBaseURL,
|
||||
Proxy: v.Proxy,
|
||||
AllowFrom: v.AllowFrom,
|
||||
ReasoningChannelID: v.ReasoningChannelID,
|
||||
}, WeixinSecurity{
|
||||
Token: v.Token,
|
||||
}
|
||||
}
|
||||
|
||||
type wecomappConfigV0 struct {
|
||||
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_WECOM_APP_ENABLED"`
|
||||
CorpID string `json:"corp_id" env:"PICOCLAW_CHANNELS_WECOM_APP_CORP_ID"`
|
||||
|
||||
@@ -443,6 +443,13 @@ func TestDefaultConfig_CronAllowCommandEnabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig_LogLevel(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
if cfg.Agents.Defaults.LogLevel != "fatal" {
|
||||
t.Errorf("LogLevel = %q, want \"fatal\"", cfg.Agents.Defaults.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_ExecAllowRemoteDefaultsTrueWhenUnset(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
configPath := filepath.Join(dir, "config.json")
|
||||
@@ -1052,3 +1059,38 @@ func TestLoadConfig_UsesPassphraseProvider(t *testing.T) {
|
||||
t.Errorf("api_key = %q, want %q", cfg.ModelList[0].APIKey(), plainKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigParsesLogLevel(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "config.json")
|
||||
data := `{"version":1,"agents":{"defaults":{"log_level":"debug"}}}`
|
||||
if err := os.WriteFile(cfgPath, []byte(data), 0o600); err != nil {
|
||||
t.Fatalf("setup: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := LoadConfig(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig: %v", err)
|
||||
}
|
||||
if cfg.Agents.Defaults.LogLevel != "debug" {
|
||||
t.Errorf("LogLevel = %q, want \"debug\"", cfg.Agents.Defaults.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLogLevelEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "config.json")
|
||||
data := `{}`
|
||||
if err := os.WriteFile(cfgPath, []byte(data), 0o600); err != nil {
|
||||
t.Fatalf("setup: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := LoadConfig(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig: %v", err)
|
||||
}
|
||||
// When config omits log_level, the DefaultConfig value ("fatal") is preserved.
|
||||
if cfg.Agents.Defaults.LogLevel != "fatal" {
|
||||
t.Errorf("LogLevel = %q, want \"fatal\"", cfg.Agents.Defaults.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func DefaultConfig() *Config {
|
||||
Version: CurrentVersion,
|
||||
Agents: AgentsConfig{
|
||||
Defaults: AgentDefaults{
|
||||
LogLevel: "fatal",
|
||||
Workspace: workspacePath,
|
||||
RestrictToWorkspace: true,
|
||||
Provider: "",
|
||||
@@ -155,6 +156,13 @@ func DefaultConfig() *Config {
|
||||
WelcomeMessage: "Hello! I'm your AI assistant. How can I help you today?",
|
||||
ProcessingMessage: DefaultWeComAIBotProcessingMessage,
|
||||
},
|
||||
Weixin: WeixinConfig{
|
||||
Enabled: false,
|
||||
BaseURL: "https://ilinkai.weixin.qq.com/",
|
||||
CDNBaseURL: "https://novac2c.cdn.weixin.qq.com/c2c",
|
||||
AllowFrom: FlexibleStringSlice{},
|
||||
Proxy: "",
|
||||
},
|
||||
Pico: PicoConfig{
|
||||
Enabled: false,
|
||||
PingInterval: 30,
|
||||
|
||||
@@ -47,6 +47,7 @@ type ChannelsSecurity struct {
|
||||
Telegram *TelegramSecurity `yaml:"telegram,omitempty"`
|
||||
Feishu *FeishuSecurity `yaml:"feishu,omitempty"`
|
||||
Discord *DiscordSecurity `yaml:"discord,omitempty"`
|
||||
Weixin *WeixinSecurity `yaml:"weixin,omitempty"`
|
||||
QQ *QQSecurity `yaml:"qq,omitempty"`
|
||||
DingTalk *DingTalkSecurity `yaml:"dingtalk,omitempty"`
|
||||
Slack *SlackSecurity `yaml:"slack,omitempty"`
|
||||
@@ -74,6 +75,10 @@ type DiscordSecurity struct {
|
||||
Token string `yaml:"token,omitempty" env:"PICOCLAW_CHANNELS_DISCORD_TOKEN"`
|
||||
}
|
||||
|
||||
type WeixinSecurity struct {
|
||||
Token string `yaml:"token,omitempty" env:"PICOCLAW_CHANNELS_WEIXIN_TOKEN"`
|
||||
}
|
||||
|
||||
type QQSecurity struct {
|
||||
AppSecret string `yaml:"app_secret,omitempty" env:"PICOCLAW_CHANNELS_QQ_APP_SECRET"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user