// Package config 提供基于 viper 的应用配置加载,按 默认值 → 配置文件 → 环境变量 的优先级合并。 package config import ( "encoding/hex" "fmt" "log/slog" "strings" "time" "github.com/spf13/viper" ) // MasterKey wraps a 32-byte AES master key with redacted formatting to prevent // accidental log/fmt exposure. type MasterKey []byte // String redacts when used with %s / fmt.Println. func (k MasterKey) String() string { return "[REDACTED 32B]" } // GoString redacts when used with %#v. func (k MasterKey) GoString() string { return "[REDACTED 32B]" } // MarshalJSON redacts when serialized to JSON. func (k MasterKey) MarshalJSON() ([]byte, error) { return []byte(`"[REDACTED]"`), nil } // LogValue redacts when logged via slog. func (k MasterKey) LogValue() slog.Value { return slog.StringValue("[REDACTED 32B]") } // Config 是应用全局配置的根结构。 type Config struct { Env string `mapstructure:"env"` DataDir string `mapstructure:"data_dir"` MasterKey MasterKey `mapstructure:"-"` // 解码后的 32 字节,单独从 master_key 解析 HTTP HTTPConfig `mapstructure:"http"` DB DBConfig `mapstructure:"db"` Bootstrap BootstrapConfig `mapstructure:"bootstrap"` Workspace Workspace `mapstructure:"workspace"` Git Git `mapstructure:"git"` Chat Chat `mapstructure:"chat"` Storage Storage `mapstructure:"storage"` Jobs JobsConfig `mapstructure:"jobs"` Notify NotifyConfig `mapstructure:"notify"` Acp AcpConfig `mapstructure:"acp"` MCP MCPConfig `mapstructure:"mcp"` Run RunConfig `mapstructure:"run"` Terminal TerminalConfig `mapstructure:"terminal"` VCS VCSConfig `mapstructure:"vcs"` Orchestrator OrchestratorConfig `mapstructure:"orchestrator"` Crypto CryptoConfig `mapstructure:"crypto"` User UserConfig `mapstructure:"user"` CodeIndex CodeIndexConfig `mapstructure:"code_index"` Docs DocsConfig `mapstructure:"docs"` Metrics MetricsConfig `mapstructure:"metrics"` } // MetricsConfig 控制 Prometheus /metrics 端点。Enabled=false 时不挂载该路由。 // AuthToken 非空时要求 Bearer/?token 匹配,避免成本/会话数对未授权方泄露。 type MetricsConfig struct { Enabled bool `mapstructure:"enabled"` AuthToken string `mapstructure:"auth_token"` } // CodeIndexConfig controls the pgvector code index + project memory embeddings. // EmbeddingEndpointID/EmbeddingModel select an OpenAI-compatible or Gemini // endpoint for embeddings (Anthropic has no embeddings API). When the endpoint // is empty, the feature degrades: search_code is disabled and memory_search // falls back to keyword/tag matching. type CodeIndexConfig struct { Enabled bool `mapstructure:"enabled"` EmbeddingEndpointID string `mapstructure:"embedding_endpoint_id"` // llm_endpoints.id (UUID); empty disables embeddings EmbeddingModel string `mapstructure:"embedding_model"` // model id producing PlatformEmbeddingDims (1536) vectors ChunkWindowLines int `mapstructure:"chunk_window_lines"` ChunkOverlapLines int `mapstructure:"chunk_overlap_lines"` MaxFileBytes int `mapstructure:"max_file_bytes"` SearchTopKCap int `mapstructure:"search_top_k_cap"` GrepMaxMatches int `mapstructure:"grep_max_matches"` GrepMaxFileBytes int `mapstructure:"grep_max_file_bytes"` } // DocsConfig controls auto doc / PR-summary generation on commit. type DocsConfig struct { Enabled bool `mapstructure:"enabled"` } // CryptoConfig 选择 secret 加密的 key provider(env|vault|kms)。key 版本元数据 // 落在 crypto_keys 表;key 材料留在 env/KMS。vault/kms 的客户端尚未接入,选中时 // 退回 env provider(见 app.go),以保持启动不破。 type CryptoConfig struct { Provider string `mapstructure:"provider"` // env(默认)| vault | kms // Vault/KMS 的连接参数预留位(接入真实客户端时填充)。 Vault VaultCfg `mapstructure:"vault"` KMS KMSCfg `mapstructure:"kms"` } // VaultCfg 是 HashiCorp Vault transit 的预留配置。 type VaultCfg struct { Address string `mapstructure:"address"` Token string `mapstructure:"token"` KeyName string `mapstructure:"key_name"` } // KMSCfg 是云 KMS 的预留配置。 type KMSCfg struct { KeyID string `mapstructure:"key_id"` Region string `mapstructure:"region"` } // UserConfig 控制 user 模块:当前仅登录暴力破解节流。 type UserConfig struct { LoginThrottle LoginThrottleCfg `mapstructure:"login_throttle"` } // LoginThrottleCfg 控制登录失败计数窗口。Enabled=false 时不节流(开发/测试)。 type LoginThrottleCfg struct { Enabled bool `mapstructure:"enabled"` MaxFailures int `mapstructure:"max_failures"` Window time.Duration `mapstructure:"window"` } // OrchestratorConfig 控制编排器模块:开关、每阶段最大尝试、回合超时、子任务深度与 // fan-out 上限。每阶段默认 agent-kind 由 PhaseAgentKinds(agent-kind 名→阶段)映射, // app 层按名解析为 UUID 注入。 type OrchestratorConfig struct { Enabled bool `mapstructure:"enabled"` MaxAttemptsPerPhase int `mapstructure:"max_attempts_per_phase"` TurnTimeout time.Duration `mapstructure:"turn_timeout"` MaxDepth int `mapstructure:"max_depth"` FanOutLimit int `mapstructure:"fan_out_limit"` // PhaseAgentKinds 把阶段名映射到 agent-kind 名(如 planning→"claude")。app 层在 // 装配时按名解析为 agent_kind_id,作为 run.config 未覆盖时的默认值。空 map 表示无 // 默认:每个 run 必须在 config.PhaseAgentKinds 显式提供。 PhaseAgentKinds map[string]string `mapstructure:"phase_agent_kinds"` // Scheduler 控制任务分解的并行调度器(autonomy roadmap §10)。 Scheduler SchedulerConfig `mapstructure:"scheduler"` } // SchedulerConfig 控制任务分解并行调度器:拓扑选取就绪子任务、扇出到并行 ACP session。 type SchedulerConfig struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` // MaxFanoutPerTick 每个 project 每 tick 最多派发的 session 数(<=0 默认 8)。 MaxFanoutPerTick int `mapstructure:"max_fanout_per_tick"` // MaxConcurrentPerProject project 内活跃 session 软上限(<=0 不预检,仅靠 acp 层硬上限)。 MaxConcurrentPerProject int `mapstructure:"max_concurrent_per_project"` } // HTTPConfig 描述 HTTP 服务监听参数及可选的 TLS / CORS / 安全头中间件。 type HTTPConfig struct { Addr string `mapstructure:"addr"` TLS TLSConfig `mapstructure:"tls"` CORS CORSCfg `mapstructure:"cors"` Security SecurityHdrCfg `mapstructure:"security"` } // TLSConfig 控制是否以 HTTPS 提供服务。Enabled=true 时 cmd/server 用 // ListenAndServeTLS,读取 CertFile/KeyFile。 type TLSConfig struct { Enabled bool `mapstructure:"enabled"` CertFile string `mapstructure:"cert_file"` KeyFile string `mapstructure:"key_file"` } // CORSCfg 控制跨域中间件。默认 off,避免误配破坏 SPA + WS 握手。 type CORSCfg struct { Enabled bool `mapstructure:"enabled"` AllowedOrigins []string `mapstructure:"allowed_origins"` AllowedMethods []string `mapstructure:"allowed_methods"` AllowedHeaders []string `mapstructure:"allowed_headers"` AllowCredentials bool `mapstructure:"allow_credentials"` MaxAgeSeconds int `mapstructure:"max_age_seconds"` } // SecurityHdrCfg 控制安全响应头中间件。HSTS 仅在 TLS 启用时下发(由 app 层 // 据 TLS.Enabled 传入)。 type SecurityHdrCfg struct { Enabled bool `mapstructure:"enabled"` HSTSMaxAgeSeconds int `mapstructure:"hsts_max_age_seconds"` FrameOptions string `mapstructure:"frame_options"` ReferrerPolicy string `mapstructure:"referrer_policy"` ContentSecurityPolicy string `mapstructure:"content_security_policy"` } // DBConfig 描述数据库连接参数。 type DBConfig struct { DSN string `mapstructure:"dsn"` } // BootstrapConfig 描述首次启动注入的管理员账号。 type BootstrapConfig struct { AdminEmail string `mapstructure:"admin_email"` AdminPassword string `mapstructure:"admin_password"` } // Workspace 控制 workspace 模块的磁盘与超时(数据目录、每个 git verb 的超时)。 // DataDir 是 workspace clone/worktree 落盘根目录;五个 timeout 直接灌进 git.Config。 type Workspace struct { DataDir string `mapstructure:"data_dir"` CloneTimeout time.Duration `mapstructure:"clone_timeout"` FetchTimeout time.Duration `mapstructure:"fetch_timeout"` PushTimeout time.Duration `mapstructure:"push_timeout"` OpsTimeout time.Duration `mapstructure:"ops_timeout"` } // Git 控制 git CLI 二进制位置(默认 "git")。 type Git struct { Binary string `mapstructure:"binary"` } // VCSConfig 配置 git-host(VCS)REST 客户端与合并网关策略。v1 仅支持 Gitea。 // RequireCIPass 默认 false:未接 CI 的仓库不会因 ci_state=unknown 永久卡住合并。 type VCSConfig struct { Gitea GiteaConfig `mapstructure:"gitea"` RequireCIPass bool `mapstructure:"require_ci_pass"` } // GiteaConfig 是 Gitea provider 的参数。Token 是平台级 PAT(建议组织/管理员 // token),BaseURL 默认指向自托管实例 git.jerryyan.net。 type GiteaConfig struct { BaseURL string `mapstructure:"base_url"` Token string `mapstructure:"token"` Timeout time.Duration `mapstructure:"timeout"` MergeMethod string `mapstructure:"merge_method"` } // Chat 配置 chat 模块的附件、流式、历史与定时清理策略。 type Chat struct { Attachment ChatAttachment `mapstructure:"attachment"` Stream ChatStream `mapstructure:"stream"` History ChatHistory `mapstructure:"history"` } // ChatAttachment 控制单文件 / 单消息上限与允许的 MIME 白名单。 type ChatAttachment struct { MaxFileSizeMB int64 `mapstructure:"max_file_size_mb"` MaxMessageSizeMB int64 `mapstructure:"max_message_size_mb"` MimeWhitelist []string `mapstructure:"mime_whitelist"` } // ChatStream 控制 SSE ring buffer 保留时长与取消宽限期。 type ChatStream struct { RingBufferRetentionSeconds int `mapstructure:"ring_buffer_retention_seconds"` } // ChatHistory 控制 token 预算的安全余量百分比(如 5 表示保留 5% 余量)。 type ChatHistory struct { SafetyMarginPct float64 `mapstructure:"safety_margin_pct"` } // Storage 选择附件落盘驱动与对应参数。 type Storage struct { Driver string `mapstructure:"driver"` // 当前仅支持 "localfs" LocalFS StorageLocalFS `mapstructure:"localfs"` } // StorageLocalFS 是 driver=localfs 时的根目录。 type StorageLocalFS struct { BasePath string `mapstructure:"base_path"` } // JobsConfig 控制后台 job runner 的总开关、worker 池与各定时任务参数。 type JobsConfig struct { Enabled bool `mapstructure:"enabled"` Workers int `mapstructure:"workers"` ShutdownGrace time.Duration `mapstructure:"shutdown_grace"` WorkspaceFetch RunnerCfg `mapstructure:"workspace_fetch"` WorktreePrune WorktreePruneCfg `mapstructure:"worktree_prune"` AttachmentCleanup AttachmentCleanupCfg `mapstructure:"attachment_cleanup"` JobReaper JobReaperCfg `mapstructure:"job_reaper"` JobPurge JobPurgeCfg `mapstructure:"job_purge"` AcpEventsPurge AcpEventsPurgeCfg `mapstructure:"acp_events_purge"` MCPTokensPurge MCPTokensPurgeCfg `mapstructure:"mcp_tokens_purge"` AcpSessionReaper AcpSessionReaperCfg `mapstructure:"acp_session_reaper"` AuditRetention AuditRetentionCfg `mapstructure:"audit_retention"` } // AuditRetentionCfg 控制审计日志保留期清理:删除早于 Retention 的 audit_logs 行。 type AuditRetentionCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` Retention time.Duration `mapstructure:"retention"` } type RunnerCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` } type WorktreePruneCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` IdleThreshold time.Duration `mapstructure:"idle_threshold"` WarningLead time.Duration `mapstructure:"warning_lead"` } type AttachmentCleanupCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` Retention time.Duration `mapstructure:"retention"` } type JobReaperCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` LeaseTimeout time.Duration `mapstructure:"lease_timeout"` } type JobPurgeCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` CompletedRetention time.Duration `mapstructure:"completed_retention"` } type AcpEventsPurgeCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` Retention time.Duration `mapstructure:"retention"` } type MCPTokensPurgeCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` Retention time.Duration `mapstructure:"retention"` } // AcpSessionReaperCfg 控制 ACP 会话回收器:墙钟超时 + 空闲超时终止。 type AcpSessionReaperCfg struct { Enabled bool `mapstructure:"enabled"` Interval time.Duration `mapstructure:"interval"` WallClockTimeout time.Duration `mapstructure:"wall_clock_timeout"` IdleTimeout time.Duration `mapstructure:"idle_timeout"` } // NotifyConfig 是 notify 模块的可选外发通道配置:webhook(jobs 异步投递)、 // email(SMTP)、im(per-user 加密 webhook)。email/im 默认关闭,未配置时 // 对应 Notifier 不注册到 Dispatcher,行为与历史一致。 type NotifyConfig struct { Webhook WebhookCfg `mapstructure:"webhook"` Email EmailCfg `mapstructure:"email"` IM IMCfg `mapstructure:"im"` } // WebhookCfg 是 webhook 通道的配置。enabled=false 时上层 dispatcher 仍会丢弃所有 webhook 消息。 type WebhookCfg struct { Enabled bool `mapstructure:"enabled"` URL string `mapstructure:"url"` Secret string `mapstructure:"secret"` Timeout time.Duration `mapstructure:"timeout"` Topics []string `mapstructure:"topics"` } // EmailCfg 是 SMTP email 通道的配置。Enabled=false(默认)时不注册 EmailNotifier, // 不发任何邮件。Username/Password 为空表示无认证 SMTP(如本地中继)。 type EmailCfg struct { Enabled bool `mapstructure:"enabled"` SMTPHost string `mapstructure:"smtp_host"` Port int `mapstructure:"port"` From string `mapstructure:"from"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` } // IMCfg 是 IM(通用 webhook,如 Slack incoming webhook)通道的配置。 // Enabled=false(默认)时不注册 IMNotifier。投递地址是 per-user 的 // im_webhook_url(加密落库于 notification_prefs),此处仅做全局开关。 type IMCfg struct { Enabled bool `mapstructure:"enabled"` } // AcpConfig 控制 ACP 模块的并发限流、子进程超时、WS 心跳与事件保留。 type AcpConfig struct { Enabled bool `mapstructure:"enabled"` MaxActiveGlobal int `mapstructure:"max_active_global"` MaxActivePerUser int `mapstructure:"max_active_per_user"` SpawnTimeout time.Duration `mapstructure:"spawn_timeout"` KillGrace time.Duration `mapstructure:"kill_grace"` StderrBufferLines int `mapstructure:"stderr_buffer_lines"` StderrTailBytes int `mapstructure:"stderr_tail_bytes"` WSPingInterval time.Duration `mapstructure:"ws_ping_interval"` WSPongTimeout time.Duration `mapstructure:"ws_pong_timeout"` WSSendBuffer int `mapstructure:"ws_send_buffer"` EventMaxPayload int `mapstructure:"event_max_payload_bytes"` EventTruncateField int `mapstructure:"event_truncate_field_bytes"` EventRetentionDays int `mapstructure:"event_retention_days"` ShutdownGrace time.Duration `mapstructure:"shutdown_grace"` PermissionTimeout time.Duration `mapstructure:"permission_timeout"` // BriefTokenBudget 是服务端组装 ACP session 初始 prompt 简报的 token 硬上限。 BriefTokenBudget int `mapstructure:"brief_token_budget"` // DefaultProjectBudgetUSD 是 per-project ACP 累计花费的全局软上限(0 = 不限)。 // 当 session/agent-kind 均未设置成本上限时作为预算判定基线。 DefaultProjectBudgetUSD float64 `mapstructure:"default_project_budget_usd"` // Sandbox 控制 per-session 执行沙箱(none|uid|container)。默认 none: // Windows 开发 / CI / 任何非 Linux 机器都跑 none,行为与历史一致。 Sandbox SandboxCfg `mapstructure:"sandbox"` } // SandboxCfg 控制 ACP 子进程的 per-session 隔离。mode=none 时全部字段被忽略。 type SandboxCfg struct { Mode string `mapstructure:"mode"` // none(默认)| uid | container BaseUID int `mapstructure:"base_uid"` // 低权 UID 基址;per-session uid = base + offset // ProxyURL 注入子进程 env 的 HTTP(S)_PROXY(egress 白名单代理);空 = 不限制。 ProxyURL string `mapstructure:"proxy_url"` // 资源上限(OS rlimit / cgroup)。0 = 不设该项。 AddressSpaceBytes uint64 `mapstructure:"address_space_bytes"` NProc uint64 `mapstructure:"nproc"` CPUSeconds uint64 `mapstructure:"cpu_seconds"` PIDs uint64 `mapstructure:"pids"` DiskBytes uint64 `mapstructure:"disk_bytes"` } // RunConfig 控制 workspace run profiles 模块:进程托管的终止宽限、日志环形缓冲 // 容量、WS 发送缓冲、关停宽限,以及传递给被托管命令的宿主环境变量白名单。 type RunConfig struct { Enabled bool `mapstructure:"enabled"` KillGrace time.Duration `mapstructure:"kill_grace"` OutBufferLines int `mapstructure:"out_buffer_lines"` OutTailBytes int `mapstructure:"out_tail_bytes"` WSSendBuffer int `mapstructure:"ws_send_buffer"` ShutdownGrace time.Duration `mapstructure:"shutdown_grace"` // EnvWhitelist 是允许从宿主进程透传给被托管命令的环境变量名白名单。 // APP_MASTER_KEY、DB DSN、git 凭据等敏感变量绝不应出现在此名单中。 EnvWhitelist []string `mapstructure:"env_whitelist"` // Exec 控制一次性 exec 原语(run_command / run_tests MCP 工具)。 Exec ExecConfig `mapstructure:"exec"` } // ExecConfig 控制 agent 自验证用的一次性 exec(run_command / run_tests): // 默认 / 最大超时、输出捕获上限,以及命令二进制白名单。AllowedCommands 为空表示 // 显式不限制命令;默认配置保持非空,生产环境不应放空。 type ExecConfig struct { DefaultTimeout time.Duration `mapstructure:"default_timeout"` MaxTimeout time.Duration `mapstructure:"max_timeout"` MaxOutputBytes int `mapstructure:"max_output_bytes"` // AllowedCommands 是允许执行的命令 base name 白名单(如 go / npm / pnpm)。 // 为空时不做命令限制(仍受 env 白名单、worktree 路径、超时与树终止约束)。 AllowedCommands []string `mapstructure:"allowed_commands"` } // TerminalConfig 控制仅限管理员的网页端终端模块:开关、启动的 shell、并发会话上限、 // 整组终止宽限,以及透传给 shell 的宿主环境变量白名单(沿用 RunConfig 的安全取舍)。 type TerminalConfig struct { Enabled bool `mapstructure:"enabled"` Shell string `mapstructure:"shell"` MaxSessions int `mapstructure:"max_sessions"` KillGrace time.Duration `mapstructure:"kill_grace"` // EnvWhitelist 是允许透传给交互式 shell 的环境变量名白名单。 // APP_MASTER_KEY、DB DSN、git 凭据等敏感变量绝不应出现在此名单中。 EnvWhitelist []string `mapstructure:"env_whitelist"` } // MCPConfig 控制 MCP 模块的开关、对外 URL(注入 ACP 子进程 env)、system token TTL 与速率限制。 type MCPConfig struct { Enabled bool `mapstructure:"enabled"` PublicURL string `mapstructure:"public_url"` SystemTokenTTL time.Duration `mapstructure:"system_token_ttl"` RateLimit MCPRateLimitCfg `mapstructure:"rate_limit"` } // MCPRateLimitCfg 控制 token bucket 容量。两层独立桶:per-token + 全局。 type MCPRateLimitCfg struct { PerTokenPerMinute int `mapstructure:"per_token_per_minute"` GlobalPerMinute int `mapstructure:"global_per_minute"` } // Load 从(按优先级递增)默认值 → 配置文件(如有)→ 环境变量 加载配置。 // configFile 可为空字符串,仅用环境变量。 func Load(configFile string) (*Config, error) { v := viper.New() // 默认值 v.SetDefault("env", "development") v.SetDefault("data_dir", "./data") v.SetDefault("http.addr", ":8080") v.SetDefault("workspace.data_dir", "./data") v.SetDefault("workspace.clone_timeout", "30m") v.SetDefault("workspace.fetch_timeout", "5m") v.SetDefault("workspace.push_timeout", "5m") v.SetDefault("workspace.ops_timeout", "60s") v.SetDefault("git.binary", "git") v.SetDefault("vcs.gitea.base_url", "https://git.jerryyan.net") v.SetDefault("vcs.gitea.timeout", "15s") v.SetDefault("vcs.gitea.merge_method", "merge") v.SetDefault("vcs.require_ci_pass", false) v.SetDefault("chat.attachment.max_file_size_mb", 20) v.SetDefault("chat.attachment.max_message_size_mb", 50) v.SetDefault("chat.attachment.mime_whitelist", []string{ "image/png", "image/jpeg", "image/webp", "image/gif", "application/pdf", "text/*", }) v.SetDefault("chat.stream.ring_buffer_retention_seconds", 300) v.SetDefault("chat.history.safety_margin_pct", 5.0) v.SetDefault("storage.driver", "localfs") v.SetDefault("storage.localfs.base_path", "./data/uploads") v.SetDefault("jobs.enabled", true) v.SetDefault("jobs.workers", 2) v.SetDefault("jobs.shutdown_grace", "30s") v.SetDefault("jobs.workspace_fetch.enabled", true) v.SetDefault("jobs.workspace_fetch.interval", "5m") v.SetDefault("jobs.worktree_prune.enabled", true) v.SetDefault("jobs.worktree_prune.interval", "24h") v.SetDefault("jobs.worktree_prune.idle_threshold", "720h") v.SetDefault("jobs.worktree_prune.warning_lead", "72h") v.SetDefault("jobs.attachment_cleanup.enabled", true) v.SetDefault("jobs.attachment_cleanup.interval", "24h") v.SetDefault("jobs.attachment_cleanup.retention", "720h") v.SetDefault("jobs.job_reaper.enabled", true) v.SetDefault("jobs.job_reaper.interval", "1m") // 15m:必须大于 orchestrator.turn_timeout(10m) 才能容纳一次完整的 agent 回合—— // jobs handler 超时 = lease_timeout*9/10 = 13.5m > 10m。否则编排器步骤会被中途 reap // 并重复执行(见 app.go 启动期不变量校验)。 v.SetDefault("jobs.job_reaper.lease_timeout", "15m") v.SetDefault("jobs.job_purge.enabled", true) v.SetDefault("jobs.job_purge.interval", "24h") v.SetDefault("jobs.job_purge.completed_retention", "168h") v.SetDefault("jobs.acp_events_purge.enabled", true) v.SetDefault("jobs.acp_events_purge.interval", "24h") v.SetDefault("jobs.acp_events_purge.retention", "168h") v.SetDefault("jobs.mcp_tokens_purge.enabled", true) v.SetDefault("jobs.mcp_tokens_purge.interval", "24h") v.SetDefault("jobs.mcp_tokens_purge.retention", "168h") v.SetDefault("jobs.acp_session_reaper.enabled", true) v.SetDefault("jobs.acp_session_reaper.interval", "1m") v.SetDefault("jobs.acp_session_reaper.wall_clock_timeout", "4h") v.SetDefault("jobs.acp_session_reaper.idle_timeout", "30m") // 审计日志保留期清理:默认 90 天,每日扫描一次。 v.SetDefault("jobs.audit_retention.enabled", true) v.SetDefault("jobs.audit_retention.interval", "24h") v.SetDefault("jobs.audit_retention.retention", "2160h") // Prometheus /metrics:默认开启(绑定内网 / 反代鉴权场景留空 token)。 v.SetDefault("metrics.enabled", true) v.SetDefault("metrics.auth_token", "") v.SetDefault("notify.webhook.enabled", false) v.SetDefault("notify.webhook.timeout", "10s") // 默认转发的 webhook topic:phase 流转事件供下游自动化(如触发 ACP run)订阅。 v.SetDefault("notify.webhook.topics", []string{"requirement.phase_change"}) // email / im 通道默认关闭,未显式启用时不注册对应 Notifier。 v.SetDefault("notify.email.enabled", false) v.SetDefault("notify.email.port", 587) v.SetDefault("notify.im.enabled", false) v.SetDefault("acp.enabled", true) v.SetDefault("acp.max_active_global", 20) v.SetDefault("acp.max_active_per_user", 3) v.SetDefault("acp.spawn_timeout", "30s") v.SetDefault("acp.kill_grace", "5s") v.SetDefault("acp.stderr_buffer_lines", 100) v.SetDefault("acp.stderr_tail_bytes", 2000) v.SetDefault("acp.ws_ping_interval", "30s") v.SetDefault("acp.ws_pong_timeout", "60s") v.SetDefault("acp.ws_send_buffer", 256) v.SetDefault("acp.event_max_payload_bytes", 65536) v.SetDefault("acp.event_truncate_field_bytes", 4096) v.SetDefault("acp.event_retention_days", 7) v.SetDefault("acp.shutdown_grace", "30s") v.SetDefault("acp.permission_timeout", "5m") v.SetDefault("acp.brief_token_budget", 6000) v.SetDefault("acp.default_project_budget_usd", 0) // 沙箱默认 none:Windows 开发 / CI / 非 Linux 机器均不隔离,行为与历史一致。 v.SetDefault("acp.sandbox.mode", "none") v.SetDefault("acp.sandbox.base_uid", 0) v.SetDefault("acp.sandbox.proxy_url", "") v.SetDefault("acp.sandbox.address_space_bytes", 0) v.SetDefault("acp.sandbox.nproc", 0) v.SetDefault("acp.sandbox.cpu_seconds", 0) v.SetDefault("acp.sandbox.pids", 0) v.SetDefault("acp.sandbox.disk_bytes", 0) // secret key provider:默认 env(沿用 APP_MASTER_KEY 为 version 1)。 v.SetDefault("crypto.provider", "env") // 登录暴力破解节流:默认开启,15 分钟窗口内 10 次失败即 429。 v.SetDefault("user.login_throttle.enabled", true) v.SetDefault("user.login_throttle.max_failures", 10) v.SetDefault("user.login_throttle.window", "15m") // HTTP TLS / CORS / 安全头:默认 off(HTTP,无 CORS);安全头默认开启(透传安全)。 v.SetDefault("http.tls.enabled", false) v.SetDefault("http.cors.enabled", false) v.SetDefault("http.cors.allow_credentials", true) v.SetDefault("http.cors.max_age_seconds", 600) v.SetDefault("http.security.enabled", true) v.SetDefault("http.security.frame_options", "DENY") v.SetDefault("http.security.referrer_policy", "no-referrer") v.SetDefault("orchestrator.enabled", true) v.SetDefault("orchestrator.max_attempts_per_phase", 3) v.SetDefault("orchestrator.turn_timeout", "10m") v.SetDefault("orchestrator.max_depth", 3) v.SetDefault("orchestrator.fan_out_limit", 3) v.SetDefault("orchestrator.phase_agent_kinds", map[string]string{}) // 任务分解并行调度器:默认关闭(须显式开启,配合 jobs 周期 RunnerFn 驱动)。 v.SetDefault("orchestrator.scheduler.enabled", false) v.SetDefault("orchestrator.scheduler.interval", "30s") v.SetDefault("orchestrator.scheduler.max_fanout_per_tick", 8) v.SetDefault("orchestrator.scheduler.max_concurrent_per_project", 4) v.SetDefault("mcp.enabled", true) v.SetDefault("mcp.public_url", "http://localhost:8080/mcp") v.SetDefault("mcp.system_token_ttl", "24h") v.SetDefault("mcp.rate_limit.per_token_per_minute", 100) v.SetDefault("mcp.rate_limit.global_per_minute", 1000) v.SetDefault("run.enabled", true) v.SetDefault("run.kill_grace", "5s") v.SetDefault("run.out_buffer_lines", 1000) v.SetDefault("run.out_tail_bytes", 2000) v.SetDefault("run.ws_send_buffer", 256) v.SetDefault("run.shutdown_grace", "30s") v.SetDefault("run.env_whitelist", []string{ "PATH", "HOME", "LANG", "LC_ALL", "TZ", "USER", "SHELL", "SystemRoot", "TEMP", "TMP", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "PATHEXT", "ComSpec", "NUMBER_OF_PROCESSORS", }) v.SetDefault("run.exec.default_timeout", "60s") v.SetDefault("run.exec.max_timeout", "600s") v.SetDefault("run.exec.max_output_bytes", 1<<20) v.SetDefault("run.exec.allowed_commands", []string{ "go", "npm", "pnpm", "yarn", "node", "python", "python3", "pytest", "uv", "cargo", "rustc", "make", "cmake", "ctest", "git", }) v.SetDefault("terminal.enabled", true) v.SetDefault("terminal.shell", "/bin/bash") // Windows 开发环境经 APP_TERMINAL_SHELL 覆盖 v.SetDefault("terminal.max_sessions", 5) v.SetDefault("terminal.kill_grace", "5s") // 透传 PATH/HOME/TERM + npm 全局安装所需(NPM_CONFIG_PREFIX 指向持久卷), // 以便容器内 `npm i -g ` 可用。敏感变量不在名单内。 v.SetDefault("terminal.env_whitelist", []string{ "PATH", "HOME", "LANG", "LC_ALL", "TZ", "USER", "SHELL", "TERM", "NPM_CONFIG_PREFIX", "NODE_PATH", "NODE_ENV", "SystemRoot", "TEMP", "TMP", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "PATHEXT", "ComSpec", "NUMBER_OF_PROCESSORS", }) // 代码索引 / 项目记忆:默认开启,但无 embedding endpoint 时自动降级为关键字回退。 v.SetDefault("code_index.enabled", true) v.SetDefault("code_index.embedding_endpoint_id", "") v.SetDefault("code_index.embedding_model", "text-embedding-3-small") v.SetDefault("code_index.chunk_window_lines", 60) v.SetDefault("code_index.chunk_overlap_lines", 10) v.SetDefault("code_index.max_file_bytes", 512*1024) v.SetDefault("code_index.search_top_k_cap", 50) v.SetDefault("code_index.grep_max_matches", 200) v.SetDefault("code_index.grep_max_file_bytes", 1<<20) // 自动文档/PR 摘要:默认关闭(避免每次提交都调用 LLM 产生成本)。 v.SetDefault("docs.enabled", false) if configFile != "" { v.SetConfigFile(configFile) if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("read config file: %w", err) } } v.SetEnvPrefix("APP") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() // 显式绑定无默认值的嵌套键,以便 Unmarshal 能从环境变量取值。 // (AutomaticEnv 仅对 v.Get* 直接查询生效;Unmarshal 只看 viper 已知的键集合。) for _, key := range []string{ "db.dsn", "bootstrap.admin_email", "bootstrap.admin_password", "master_key", "vcs.gitea.token", } { if err := v.BindEnv(key); err != nil { return nil, fmt.Errorf("bind env %s: %w", key, err) } } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) } // MasterKey 是 hex 字符串,需要单独解码 rawKey := v.GetString("master_key") if rawKey == "" { return nil, fmt.Errorf("APP_MASTER_KEY required") } key, err := hex.DecodeString(rawKey) if err != nil { return nil, fmt.Errorf("APP_MASTER_KEY invalid hex: %w", err) } if len(key) != 32 { return nil, fmt.Errorf("APP_MASTER_KEY must be 32 bytes (got %d)", len(key)) } cfg.MasterKey = MasterKey(key) if cfg.DB.DSN == "" { return nil, fmt.Errorf("APP_DB_DSN required") } return &cfg, nil }