mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-14 05:08:58 +08:00
591 lines
23 KiB
Go
591 lines
23 KiB
Go
package config
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/caarlos0/env/v11"
|
||
)
|
||
|
||
type Config struct {
|
||
Agents AgentsConfig `json:"agents"`
|
||
Channels ChannelsConfig `json:"channels"`
|
||
Providers ProvidersConfig `json:"providers"`
|
||
Gateway GatewayConfig `json:"gateway"`
|
||
Cron CronConfig `json:"cron"`
|
||
Tools ToolsConfig `json:"tools"`
|
||
Logging LoggingConfig `json:"logging"`
|
||
Sentinel SentinelConfig `json:"sentinel"`
|
||
Memory MemoryConfig `json:"memory"`
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
type AgentsConfig struct {
|
||
Defaults AgentDefaults `json:"defaults"`
|
||
}
|
||
|
||
type AgentDefaults struct {
|
||
Workspace string `json:"workspace" env:"CLAWGO_AGENTS_DEFAULTS_WORKSPACE"`
|
||
Proxy string `json:"proxy" env:"CLAWGO_AGENTS_DEFAULTS_PROXY"`
|
||
ProxyFallbacks []string `json:"proxy_fallbacks" env:"CLAWGO_AGENTS_DEFAULTS_PROXY_FALLBACKS"`
|
||
MaxTokens int `json:"max_tokens" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||
Temperature float64 `json:"temperature" env:"CLAWGO_AGENTS_DEFAULTS_TEMPERATURE"`
|
||
MaxToolIterations int `json:"max_tool_iterations" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||
Autonomy AutonomyConfig `json:"autonomy"`
|
||
Texts AgentTextConfig `json:"texts"`
|
||
ContextCompaction ContextCompactionConfig `json:"context_compaction"`
|
||
RuntimeControl RuntimeControlConfig `json:"runtime_control"`
|
||
}
|
||
|
||
type AutonomyConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ENABLED"`
|
||
TickIntervalSec int `json:"tick_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TICK_INTERVAL_SEC"`
|
||
MinRunIntervalSec int `json:"min_run_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MIN_RUN_INTERVAL_SEC"`
|
||
MaxPendingDurationSec int `json:"max_pending_duration_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_PENDING_DURATION_SEC"`
|
||
MaxConsecutiveStalls int `json:"max_consecutive_stalls" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_CONSECUTIVE_STALLS"`
|
||
MaxDispatchPerTick int `json:"max_dispatch_per_tick" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_DISPATCH_PER_TICK"`
|
||
NotifyCooldownSec int `json:"notify_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_COOLDOWN_SEC"`
|
||
NotifySameReasonCooldownSec int `json:"notify_same_reason_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_SAME_REASON_COOLDOWN_SEC"`
|
||
QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"`
|
||
UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"`
|
||
WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"`
|
||
NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"`
|
||
NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"`
|
||
}
|
||
|
||
type AgentTextConfig struct {
|
||
NoResponseFallback string `json:"no_response_fallback"`
|
||
ThinkOnlyFallback string `json:"think_only_fallback"`
|
||
MemoryRecallKeywords []string `json:"memory_recall_keywords"`
|
||
LangUsage string `json:"lang_usage"`
|
||
LangInvalid string `json:"lang_invalid"`
|
||
LangUpdatedTemplate string `json:"lang_updated_template"`
|
||
SubagentsNone string `json:"subagents_none"`
|
||
SessionsNone string `json:"sessions_none"`
|
||
UnsupportedAction string `json:"unsupported_action"`
|
||
SystemRewriteTemplate string `json:"system_rewrite_template"`
|
||
RuntimeCompactionNote string `json:"runtime_compaction_note"`
|
||
StartupCompactionNote string `json:"startup_compaction_note"`
|
||
AutonomyImportantKeywords []string `json:"autonomy_important_keywords"`
|
||
AutonomyCompletionTemplate string `json:"autonomy_completion_template"`
|
||
AutonomyBlockedTemplate string `json:"autonomy_blocked_template"`
|
||
}
|
||
|
||
type HeartbeatConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ENABLED"`
|
||
EverySec int `json:"every_sec" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_EVERY_SEC"`
|
||
AckMaxChars int `json:"ack_max_chars" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ACK_MAX_CHARS"`
|
||
PromptTemplate string `json:"prompt_template" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_PROMPT_TEMPLATE"`
|
||
}
|
||
|
||
type RuntimeControlConfig struct {
|
||
IntentMaxInputChars int `json:"intent_max_input_chars" env:"CLAWGO_INTENT_MAX_INPUT_CHARS"`
|
||
AutonomyTickIntervalSec int `json:"autonomy_tick_interval_sec" env:"CLAWGO_AUTONOMY_TICK_INTERVAL_SEC"`
|
||
AutonomyMinRunIntervalSec int `json:"autonomy_min_run_interval_sec" env:"CLAWGO_AUTONOMY_MIN_RUN_INTERVAL_SEC"`
|
||
AutonomyIdleThresholdSec int `json:"autonomy_idle_threshold_sec" env:"CLAWGO_AUTONOMY_IDLE_THRESHOLD_SEC"`
|
||
AutonomyMaxRoundsWithoutUser int `json:"autonomy_max_rounds_without_user" env:"CLAWGO_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"`
|
||
AutonomyMaxPendingDurationSec int `json:"autonomy_max_pending_duration_sec" env:"CLAWGO_AUTONOMY_MAX_PENDING_DURATION_SEC"`
|
||
AutonomyMaxConsecutiveStalls int `json:"autonomy_max_consecutive_stalls" env:"CLAWGO_AUTONOMY_MAX_STALLS"`
|
||
AutoLearnMaxRoundsWithoutUser int `json:"autolearn_max_rounds_without_user" env:"CLAWGO_AUTOLEARN_MAX_ROUNDS_WITHOUT_USER"`
|
||
RunStateTTLSeconds int `json:"run_state_ttl_seconds" env:"CLAWGO_RUN_STATE_TTL_SECONDS"`
|
||
RunStateMax int `json:"run_state_max" env:"CLAWGO_RUN_STATE_MAX"`
|
||
ToolParallelSafeNames []string `json:"tool_parallel_safe_names"`
|
||
ToolMaxParallelCalls int `json:"tool_max_parallel_calls"`
|
||
SystemSummary SystemSummaryPolicyConfig `json:"system_summary"`
|
||
}
|
||
|
||
type SystemSummaryPolicyConfig struct {
|
||
Marker string `json:"marker"`
|
||
CompletedPrefix string `json:"completed_prefix"`
|
||
ChangesPrefix string `json:"changes_prefix"`
|
||
OutcomePrefix string `json:"outcome_prefix"`
|
||
CompletedTitle string `json:"completed_title"`
|
||
ChangesTitle string `json:"changes_title"`
|
||
OutcomesTitle string `json:"outcomes_title"`
|
||
}
|
||
|
||
type ContextCompactionConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_ENABLED"`
|
||
Mode string `json:"mode" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MODE"`
|
||
TriggerMessages int `json:"trigger_messages" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_TRIGGER_MESSAGES"`
|
||
KeepRecentMessages int `json:"keep_recent_messages" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_KEEP_RECENT_MESSAGES"`
|
||
MaxSummaryChars int `json:"max_summary_chars" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MAX_SUMMARY_CHARS"`
|
||
MaxTranscriptChars int `json:"max_transcript_chars" env:"CLAWGO_AGENTS_DEFAULTS_CONTEXT_COMPACTION_MAX_TRANSCRIPT_CHARS"`
|
||
}
|
||
|
||
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"`
|
||
}
|
||
|
||
type WhatsAppConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_WHATSAPP_ENABLED"`
|
||
BridgeURL string `json:"bridge_url" env:"CLAWGO_CHANNELS_WHATSAPP_BRIDGE_URL"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_WHATSAPP_ALLOW_FROM"`
|
||
}
|
||
|
||
type TelegramConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_TELEGRAM_ENABLED"`
|
||
Token string `json:"token" env:"CLAWGO_CHANNELS_TELEGRAM_TOKEN"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_TELEGRAM_ALLOW_FROM"`
|
||
AllowChats []string `json:"allow_chats" env:"CLAWGO_CHANNELS_TELEGRAM_ALLOW_CHATS"`
|
||
EnableGroups bool `json:"enable_groups" env:"CLAWGO_CHANNELS_TELEGRAM_ENABLE_GROUPS"`
|
||
RequireMentionInGroups bool `json:"require_mention_in_groups" env:"CLAWGO_CHANNELS_TELEGRAM_REQUIRE_MENTION_IN_GROUPS"`
|
||
}
|
||
|
||
type FeishuConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_FEISHU_ENABLED"`
|
||
AppID string `json:"app_id" env:"CLAWGO_CHANNELS_FEISHU_APP_ID"`
|
||
AppSecret string `json:"app_secret" env:"CLAWGO_CHANNELS_FEISHU_APP_SECRET"`
|
||
EncryptKey string `json:"encrypt_key" env:"CLAWGO_CHANNELS_FEISHU_ENCRYPT_KEY"`
|
||
VerificationToken string `json:"verification_token" env:"CLAWGO_CHANNELS_FEISHU_VERIFICATION_TOKEN"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_FEISHU_ALLOW_FROM"`
|
||
}
|
||
|
||
type DiscordConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_DISCORD_ENABLED"`
|
||
Token string `json:"token" env:"CLAWGO_CHANNELS_DISCORD_TOKEN"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_DISCORD_ALLOW_FROM"`
|
||
}
|
||
|
||
type MaixCamConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_MAIXCAM_ENABLED"`
|
||
Host string `json:"host" env:"CLAWGO_CHANNELS_MAIXCAM_HOST"`
|
||
Port int `json:"port" env:"CLAWGO_CHANNELS_MAIXCAM_PORT"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_MAIXCAM_ALLOW_FROM"`
|
||
}
|
||
|
||
type QQConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_QQ_ENABLED"`
|
||
AppID string `json:"app_id" env:"CLAWGO_CHANNELS_QQ_APP_ID"`
|
||
AppSecret string `json:"app_secret" env:"CLAWGO_CHANNELS_QQ_APP_SECRET"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_QQ_ALLOW_FROM"`
|
||
}
|
||
|
||
type DingTalkConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_CHANNELS_DINGTALK_ENABLED"`
|
||
ClientID string `json:"client_id" env:"CLAWGO_CHANNELS_DINGTALK_CLIENT_ID"`
|
||
ClientSecret string `json:"client_secret" env:"CLAWGO_CHANNELS_DINGTALK_CLIENT_SECRET"`
|
||
AllowFrom []string `json:"allow_from" env:"CLAWGO_CHANNELS_DINGTALK_ALLOW_FROM"`
|
||
}
|
||
|
||
type ProvidersConfig struct {
|
||
Proxy ProviderConfig `json:"proxy"`
|
||
Proxies map[string]ProviderConfig `json:"proxies"`
|
||
}
|
||
|
||
type ProviderConfig struct {
|
||
APIKey string `json:"api_key" env:"CLAWGO_PROVIDERS_{{.Name}}_API_KEY"`
|
||
APIBase string `json:"api_base" env:"CLAWGO_PROVIDERS_{{.Name}}_API_BASE"`
|
||
Protocol string `json:"protocol" env:"CLAWGO_PROVIDERS_{{.Name}}_PROTOCOL"`
|
||
Models []string `json:"models" env:"CLAWGO_PROVIDERS_{{.Name}}_MODELS"`
|
||
SupportsResponsesCompact bool `json:"supports_responses_compact" env:"CLAWGO_PROVIDERS_{{.Name}}_SUPPORTS_RESPONSES_COMPACT"`
|
||
Auth string `json:"auth" env:"CLAWGO_PROVIDERS_{{.Name}}_AUTH"`
|
||
TimeoutSec int `json:"timeout_sec" env:"CLAWGO_PROVIDERS_PROXY_TIMEOUT_SEC"`
|
||
}
|
||
|
||
type GatewayConfig struct {
|
||
Host string `json:"host" env:"CLAWGO_GATEWAY_HOST"`
|
||
Port int `json:"port" env:"CLAWGO_GATEWAY_PORT"`
|
||
Token string `json:"token" env:"CLAWGO_GATEWAY_TOKEN"`
|
||
}
|
||
|
||
type CronConfig struct {
|
||
MinSleepSec int `json:"min_sleep_sec" env:"CLAWGO_CRON_MIN_SLEEP_SEC"`
|
||
MaxSleepSec int `json:"max_sleep_sec" env:"CLAWGO_CRON_MAX_SLEEP_SEC"`
|
||
RetryBackoffBaseSec int `json:"retry_backoff_base_sec" env:"CLAWGO_CRON_RETRY_BACKOFF_BASE_SEC"`
|
||
RetryBackoffMaxSec int `json:"retry_backoff_max_sec" env:"CLAWGO_CRON_RETRY_BACKOFF_MAX_SEC"`
|
||
MaxConsecutiveFailureRetries int `json:"max_consecutive_failure_retries" env:"CLAWGO_CRON_MAX_CONSECUTIVE_FAILURE_RETRIES"`
|
||
MaxWorkers int `json:"max_workers" env:"CLAWGO_CRON_MAX_WORKERS"`
|
||
}
|
||
|
||
type WebSearchConfig struct {
|
||
APIKey string `json:"api_key" env:"CLAWGO_TOOLS_WEB_SEARCH_API_KEY"`
|
||
MaxResults int `json:"max_results" env:"CLAWGO_TOOLS_WEB_SEARCH_MAX_RESULTS"`
|
||
}
|
||
|
||
type WebToolsConfig struct {
|
||
Search WebSearchConfig `json:"search"`
|
||
}
|
||
|
||
type ShellConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_ENABLED"`
|
||
WorkingDir string `json:"working_dir" env:"CLAWGO_TOOLS_SHELL_WORKING_DIR"`
|
||
Timeout time.Duration `json:"timeout" env:"CLAWGO_TOOLS_SHELL_TIMEOUT"`
|
||
AutoInstallMissing bool `json:"auto_install_missing" env:"CLAWGO_TOOLS_SHELL_AUTO_INSTALL_MISSING"`
|
||
Sandbox SandboxConfig `json:"sandbox"`
|
||
}
|
||
|
||
type SandboxConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_TOOLS_SHELL_SANDBOX_ENABLED"`
|
||
Image string `json:"image" env:"CLAWGO_TOOLS_SHELL_SANDBOX_IMAGE"`
|
||
}
|
||
|
||
type FilesystemConfig struct{}
|
||
|
||
type ToolsConfig struct {
|
||
Web WebToolsConfig `json:"web"`
|
||
Shell ShellConfig `json:"shell"`
|
||
Filesystem FilesystemConfig `json:"filesystem"`
|
||
}
|
||
|
||
type LoggingConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_LOGGING_ENABLED"`
|
||
Dir string `json:"dir" env:"CLAWGO_LOGGING_DIR"`
|
||
Filename string `json:"filename" env:"CLAWGO_LOGGING_FILENAME"`
|
||
MaxSizeMB int `json:"max_size_mb" env:"CLAWGO_LOGGING_MAX_SIZE_MB"`
|
||
RetentionDays int `json:"retention_days" env:"CLAWGO_LOGGING_RETENTION_DAYS"`
|
||
}
|
||
|
||
type SentinelConfig struct {
|
||
Enabled bool `json:"enabled" env:"CLAWGO_SENTINEL_ENABLED"`
|
||
IntervalSec int `json:"interval_sec" env:"CLAWGO_SENTINEL_INTERVAL_SEC"`
|
||
AutoHeal bool `json:"auto_heal" env:"CLAWGO_SENTINEL_AUTO_HEAL"`
|
||
NotifyChannel string `json:"notify_channel" env:"CLAWGO_SENTINEL_NOTIFY_CHANNEL"`
|
||
NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_SENTINEL_NOTIFY_CHAT_ID"`
|
||
}
|
||
|
||
type MemoryConfig struct {
|
||
Layered bool `json:"layered" env:"CLAWGO_MEMORY_LAYERED"`
|
||
RecentDays int `json:"recent_days" env:"CLAWGO_MEMORY_RECENT_DAYS"`
|
||
Layers struct {
|
||
Profile bool `json:"profile" env:"CLAWGO_MEMORY_LAYERS_PROFILE"`
|
||
Project bool `json:"project" env:"CLAWGO_MEMORY_LAYERS_PROJECT"`
|
||
Procedures bool `json:"procedures" env:"CLAWGO_MEMORY_LAYERS_PROCEDURES"`
|
||
} `json:"layers"`
|
||
}
|
||
|
||
var (
|
||
isDebug bool
|
||
muDebug sync.RWMutex
|
||
)
|
||
|
||
func SetDebugMode(debug bool) {
|
||
muDebug.Lock()
|
||
defer muDebug.Unlock()
|
||
isDebug = debug
|
||
}
|
||
|
||
func IsDebugMode() bool {
|
||
muDebug.RLock()
|
||
defer muDebug.RUnlock()
|
||
return isDebug
|
||
}
|
||
|
||
func GetConfigDir() string {
|
||
if IsDebugMode() {
|
||
return ".clawgo"
|
||
}
|
||
home, _ := os.UserHomeDir()
|
||
return filepath.Join(home, ".clawgo")
|
||
}
|
||
|
||
func DefaultConfig() *Config {
|
||
configDir := GetConfigDir()
|
||
return &Config{
|
||
Agents: AgentsConfig{
|
||
Defaults: AgentDefaults{
|
||
Workspace: filepath.Join(configDir, "workspace"),
|
||
Proxy: "proxy",
|
||
ProxyFallbacks: []string{},
|
||
MaxTokens: 8192,
|
||
Temperature: 0.7,
|
||
MaxToolIterations: 20,
|
||
Heartbeat: HeartbeatConfig{
|
||
Enabled: true,
|
||
EverySec: 30 * 60,
|
||
AckMaxChars: 64,
|
||
PromptTemplate: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
|
||
},
|
||
Autonomy: AutonomyConfig{
|
||
Enabled: false,
|
||
TickIntervalSec: 30,
|
||
MinRunIntervalSec: 20,
|
||
MaxPendingDurationSec: 180,
|
||
MaxConsecutiveStalls: 3,
|
||
MaxDispatchPerTick: 2,
|
||
NotifyCooldownSec: 300,
|
||
NotifySameReasonCooldownSec: 900,
|
||
QuietHours: "23:00-08:00",
|
||
UserIdleResumeSec: 20,
|
||
WaitingResumeDebounceSec: 5,
|
||
NotifyChannel: "",
|
||
NotifyChatID: "",
|
||
},
|
||
Texts: AgentTextConfig{
|
||
NoResponseFallback: "I've completed processing but have no response to give.",
|
||
ThinkOnlyFallback: "Thinking process completed.",
|
||
MemoryRecallKeywords: []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"},
|
||
LangUsage: "Usage: /lang <code>",
|
||
LangInvalid: "Invalid language code.",
|
||
LangUpdatedTemplate: "Language preference updated to %s",
|
||
SubagentsNone: "No subagents.",
|
||
SessionsNone: "No sessions.",
|
||
UnsupportedAction: "unsupported action",
|
||
SystemRewriteTemplate: "Rewrite the following internal system update in concise user-facing language:\n\n%s",
|
||
RuntimeCompactionNote: "[runtime-compaction] removed %d old messages, kept %d recent messages",
|
||
StartupCompactionNote: "[startup-compaction] removed %d old messages, kept %d recent messages",
|
||
AutonomyImportantKeywords: []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"},
|
||
AutonomyCompletionTemplate: "✅ 已完成:%s\n回复“继续 %s”可继续下一步。",
|
||
AutonomyBlockedTemplate: "⚠️ 任务受阻:%s(%s)\n回复“继续 %s”我会重试。",
|
||
},
|
||
ContextCompaction: ContextCompactionConfig{
|
||
Enabled: true,
|
||
Mode: "summary",
|
||
TriggerMessages: 60,
|
||
KeepRecentMessages: 20,
|
||
MaxSummaryChars: 6000,
|
||
MaxTranscriptChars: 20000,
|
||
},
|
||
RuntimeControl: RuntimeControlConfig{
|
||
IntentMaxInputChars: 1200,
|
||
AutonomyTickIntervalSec: 20,
|
||
AutonomyMinRunIntervalSec: 20,
|
||
AutonomyIdleThresholdSec: 20,
|
||
AutonomyMaxRoundsWithoutUser: 120,
|
||
AutonomyMaxPendingDurationSec: 180,
|
||
AutonomyMaxConsecutiveStalls: 3,
|
||
AutoLearnMaxRoundsWithoutUser: 200,
|
||
RunStateTTLSeconds: 1800,
|
||
RunStateMax: 500,
|
||
ToolParallelSafeNames: []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"},
|
||
ToolMaxParallelCalls: 2,
|
||
SystemSummary: SystemSummaryPolicyConfig{
|
||
Marker: "## System Task Summary",
|
||
CompletedPrefix: "- Completed:",
|
||
ChangesPrefix: "- Changes:",
|
||
OutcomePrefix: "- Outcome:",
|
||
CompletedTitle: "Completed Actions",
|
||
ChangesTitle: "Change Summaries",
|
||
OutcomesTitle: "Execution Outcomes",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
Channels: ChannelsConfig{
|
||
WhatsApp: WhatsAppConfig{
|
||
Enabled: false,
|
||
BridgeURL: "ws://localhost:3001",
|
||
AllowFrom: []string{},
|
||
},
|
||
Telegram: TelegramConfig{
|
||
Enabled: false,
|
||
Token: "",
|
||
AllowFrom: []string{},
|
||
AllowChats: []string{},
|
||
EnableGroups: true,
|
||
RequireMentionInGroups: true,
|
||
},
|
||
Feishu: FeishuConfig{
|
||
Enabled: false,
|
||
AppID: "",
|
||
AppSecret: "",
|
||
EncryptKey: "",
|
||
VerificationToken: "",
|
||
AllowFrom: []string{},
|
||
},
|
||
Discord: DiscordConfig{
|
||
Enabled: false,
|
||
Token: "",
|
||
AllowFrom: []string{},
|
||
},
|
||
MaixCam: MaixCamConfig{
|
||
Enabled: false,
|
||
Host: "0.0.0.0",
|
||
Port: 18790,
|
||
AllowFrom: []string{},
|
||
},
|
||
QQ: QQConfig{
|
||
Enabled: false,
|
||
AppID: "",
|
||
AppSecret: "",
|
||
AllowFrom: []string{},
|
||
},
|
||
DingTalk: DingTalkConfig{
|
||
Enabled: false,
|
||
ClientID: "",
|
||
ClientSecret: "",
|
||
AllowFrom: []string{},
|
||
},
|
||
},
|
||
Providers: ProvidersConfig{
|
||
Proxy: ProviderConfig{
|
||
APIBase: "http://localhost:8080/v1",
|
||
Protocol: "chat_completions",
|
||
Models: []string{"glm-4.7"},
|
||
TimeoutSec: 90,
|
||
},
|
||
Proxies: map[string]ProviderConfig{},
|
||
},
|
||
Gateway: GatewayConfig{
|
||
Host: "0.0.0.0",
|
||
Port: 18790,
|
||
Token: "",
|
||
},
|
||
Cron: CronConfig{
|
||
MinSleepSec: 1,
|
||
MaxSleepSec: 30,
|
||
RetryBackoffBaseSec: 30,
|
||
RetryBackoffMaxSec: 1800,
|
||
MaxConsecutiveFailureRetries: 5,
|
||
MaxWorkers: 4,
|
||
},
|
||
Tools: ToolsConfig{
|
||
Web: WebToolsConfig{
|
||
Search: WebSearchConfig{
|
||
APIKey: "",
|
||
MaxResults: 5,
|
||
},
|
||
},
|
||
Shell: ShellConfig{
|
||
Enabled: true,
|
||
Timeout: 60 * time.Second,
|
||
AutoInstallMissing: false,
|
||
Sandbox: SandboxConfig{
|
||
Enabled: false,
|
||
Image: "alpine:3.20",
|
||
},
|
||
},
|
||
Filesystem: FilesystemConfig{},
|
||
},
|
||
Logging: LoggingConfig{
|
||
Enabled: true,
|
||
Dir: filepath.Join(configDir, "logs"),
|
||
Filename: "clawgo.log",
|
||
MaxSizeMB: 20,
|
||
RetentionDays: 3,
|
||
},
|
||
Sentinel: SentinelConfig{
|
||
Enabled: true,
|
||
IntervalSec: 60,
|
||
AutoHeal: true,
|
||
NotifyChannel: "",
|
||
NotifyChatID: "",
|
||
},
|
||
Memory: MemoryConfig{
|
||
Layered: true,
|
||
RecentDays: 3,
|
||
Layers: struct {
|
||
Profile bool `json:"profile" env:"CLAWGO_MEMORY_LAYERS_PROFILE"`
|
||
Project bool `json:"project" env:"CLAWGO_MEMORY_LAYERS_PROJECT"`
|
||
Procedures bool `json:"procedures" env:"CLAWGO_MEMORY_LAYERS_PROCEDURES"`
|
||
}{
|
||
Profile: true,
|
||
Project: true,
|
||
Procedures: true,
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func LoadConfig(path string) (*Config, error) {
|
||
cfg := DefaultConfig()
|
||
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return cfg, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
if err := unmarshalConfigStrict(data, cfg); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if err := env.Parse(cfg); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return cfg, nil
|
||
}
|
||
|
||
func unmarshalConfigStrict(data []byte, cfg *Config) error {
|
||
dec := json.NewDecoder(bytes.NewReader(data))
|
||
dec.DisallowUnknownFields()
|
||
if err := dec.Decode(cfg); err != nil {
|
||
return err
|
||
}
|
||
var extra json.RawMessage
|
||
if err := dec.Decode(&extra); err != io.EOF {
|
||
if err == nil {
|
||
return fmt.Errorf("invalid config: trailing JSON content")
|
||
}
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func SaveConfig(path string, cfg *Config) error {
|
||
cfg.mu.RLock()
|
||
defer cfg.mu.RUnlock()
|
||
|
||
data, err := json.MarshalIndent(cfg, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
dir := filepath.Dir(path)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return err
|
||
}
|
||
|
||
return os.WriteFile(path, data, 0644)
|
||
}
|
||
|
||
func (c *Config) WorkspacePath() string {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
return expandHome(c.Agents.Defaults.Workspace)
|
||
}
|
||
|
||
func (c *Config) GetAPIKey() string {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
return c.Providers.Proxy.APIKey
|
||
}
|
||
|
||
func (c *Config) GetAPIBase() string {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
return c.Providers.Proxy.APIBase
|
||
}
|
||
|
||
func (c *Config) LogFilePath() string {
|
||
c.mu.RLock()
|
||
defer c.mu.RUnlock()
|
||
|
||
dir := expandHome(c.Logging.Dir)
|
||
filename := c.Logging.Filename
|
||
if filename == "" {
|
||
filename = "clawgo.log"
|
||
}
|
||
return filepath.Join(dir, filename)
|
||
}
|
||
|
||
func expandHome(path string) string {
|
||
if path == "" {
|
||
return path
|
||
}
|
||
if path[0] == '~' {
|
||
home, _ := os.UserHomeDir()
|
||
if len(path) > 1 && path[1] == '/' {
|
||
return home + path[1:]
|
||
}
|
||
return home
|
||
}
|
||
return path
|
||
}
|