config

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package config loads fairpeer's runtime configuration from TOML. Resolution order: flag > project ./fairpeer.toml > user ~/.config/fairpeer/config.toml > built-in defaults. Secrets come from the environment via api_key_env and are never stored in config files.

fetch.go — model auto-discovery via the OpenAI-compatible GET /models API.

Index

Constants

View Source
const (
	ReasoningProtocolAuto   = "auto"
	ReasoningProtocolOpenAI = "openai"
	ReasoningProtocolNone   = "none"
)
View Source
const (
	EffortAuto   = "auto"
	EffortLow    = "low"
	EffortMedium = "medium"
	EffortHigh   = "high"
)

Canonical effort levels. All providers use this unified vocabulary. Provider-specific translation happens at the wire layer (openai.go, anthropic.go).

View Source
const (
	// ProfileDev is the built-in coding mode. It mirrors the unprofiled behaviour
	// exactly (empty overrides), so a config with no [[profiles]] is effectively
	// always in dev. Resolving "dev" therefore always succeeds and never mutates
	// the Controller's tool/skill/plugin set beyond what config already declares.
	ProfileDev = "dev"
	// ProfileCowork is the office mode. For Phase 0 it is intentionally a thin
	// shell: a prompt addon that biases the model toward office tasks, but the
	// SAME tool/skill/plugin set as dev. Real coWork capabilities (browser,
	// desktop automation) arrive in later phases and turn on here.
	ProfileCowork = "cowork"
)
View Source
const DefaultDistillInterval = 30

DefaultDistillInterval is the Distill run cadence when [dream].distill_interval is unset.

View Source
const DefaultDreamInterval = 7

DefaultDreamInterval is the Dream run cadence when [dream].dream_interval is unset.

View Source
const DefaultFastTaskModel = ""

DefaultFastTaskModel is the model dream/distill/rag-extract run on. Empty means unconfigured — at runtime an empty agent.fast_task_model falls back to the default model; this constant no longer hardcodes a vendor model. Phase 3 will resolve it from the configured provider's fast_model role.

View Source
const DefaultIdleMinutes = 10

DefaultIdleMinutes is how long the user must be inactive before an idle Dream run may fire, when [dream].idle_minutes is unset. Dream is meant to run in the user's downtime, not while they are actively working — this bounds resource contention and matches the "consolidate when idle" intent.

View Source
const DefaultSkillColdDays = 90

DefaultSkillColdDays is the inactivity threshold for skill retirement when [dream].skill_cold_days is unset: 90 days mirrors memory's ColdDays default.

View Source
const DefaultSystemPrompt = `` /* 1464-byte string literal not displayed */

DefaultSystemPrompt is used when config provides none.

View Source
const LanguagePolicy = `Reply in the same language the user is using in their most recent message: ` +
	`if they write in Chinese answer in Chinese, in English answer in English, and switch ` +
	`whenever they switch. Let this also guide the language you think in. Always keep code, ` +
	`identifiers, file paths, shell commands, and technical terms in their original form — never translate them.`

LanguagePolicy is the auto fallback appended to the system prompt when no concrete UI language is resolved. It is static English text, so it stays part of the cache-stable prefix and avoids per-turn language injection.

Variables

View Source
var ConventionDirs = []string{".fairpeer", ".agents", ".agent", ".claude"}

ConventionDirs are the parent directories scanned for agent assets (skills, commands), in canonical-first order. .fairpeer is ours; .agents / .agent / .claude let users drop in assets authored for other agent tools without moving files. Shared so skills (internal/skill) and commands (CommandDirs) discover the same set. Note: hooks are NOT scanned across these — a .claude/settings.json uses a different hook schema that can't be parsed as ours, so hooks stay in .fairpeer/settings.json (see internal/hook).

Functions

func ArchiveDir

func ArchiveDir() string

ArchiveDir is where compacted conversation history is archived for traceability (one timestamped .jsonl per compaction). Empty if the user config directory cannot be resolved, in which case archiving is skipped.

func BuildModelFetchURLs

func BuildModelFetchURLs(baseURL, override string) ([]string, error)

BuildModelFetchURLs derives likely OpenAI-compatible model-list endpoints. It keeps fairpeer's historical {base}/models path first, then tries the common {base}/v1/models shape used by many aggregators.

func CacheDir

func CacheDir() string

CacheDir is the per-user cache root for derived/regenerable artefacts: MCP handshake snapshots, plugin startup-latency telemetry. Lives beside the existing dirs (UserConfigDir/fairpeer/...) so the whole fairpeer state tree shares one root the user can wipe in a single rm. Empty when the OS dir is unavailable — callers must tolerate that (caching is best-effort).

func CanonicalDesktopOfficialProviderName

func CanonicalDesktopOfficialProviderName(name string) string

CanonicalDesktopOfficialProviderName returns the Settings Center provider ID for built-in official provider aliases.

func CanonicalSkillPath

func CanonicalSkillPath(path string) string

CanonicalSkillPath expands env vars, ~ and relative segments to an absolute cleaned path for comparing skill roots. On Windows it folds case so paths that differ only in casing dedupe. Use only for comparison, never as stored config.

func CommandDirs

func CommandDirs() []string

CommandDirs returns the directories scanned for custom slash commands, lowest priority first, so a later (more specific) directory overrides an earlier one on a name clash. Order: home-dir convention dirs (~/.claude/commands … ~/.fairpeer/commands), the legacy XDG user dir (~/.config/fairpeer/commands), then the project's convention dirs (.claude/commands … .fairpeer/commands). Scanning the .claude / .agents / .agent dirs lets commands authored for other agent tools (same .md + frontmatter format) work here unchanged.

func CommandDirsForRoot

func CommandDirsForRoot(root string) []string

CommandDirsForRoot is like CommandDirs but resolves the project convention dirs under root instead of the current working directory. Global (home/XDG) dirs are unchanged — they are always user-scoped.

func CoworkPromptAddon

func CoworkPromptAddon(disabledSkills []string) string

CoworkPromptAddon returns the cowork system-prompt add-on with capability routing rows for disabled skills REMOVED. Passing nil/empty yields the full add-on verbatim (no rows dropped) — the historical static behaviour.

The cowork prompt hard-codes a "for task X, call run_skill("Y")" routing table. Without this filter, disabling a skill (e.g. ppt-auto) still leaves the prompt instructing the model to call it, so the model repeatedly tries a disabled skill instead of telling the user to re-enable it. Dropping the row removes that instruction entirely.

disabledSkills is the effective disabled-name set (config + profile + whitelist-excluded, already name-keyed upstream). Names are compared via SkillNameKey so casing/platform differences don't let a row survive.

func EffectiveEffort

func EffectiveEffort(e *ProviderEntry) string

EffectiveEffort resolves the provider-visible effort value. Explicit ProviderEntry.Effort wins; otherwise a configured SupportedEfforts list makes DefaultEffort (or the first supported level) the runtime default. Empty means provider default / omit the provider-specific effort field.

func EffortDisplay

func EffortDisplay(e *ProviderEntry) string

EffortDisplay returns the selected /effort level, using "auto" for provider default.

func ExpandVars

func ExpandVars(s string) string

ExpandVars substitutes ${VAR} / ${VAR:-default} references from the process environment. An unset variable with no default expands to "" (matching the MCP / Claude Code convention), so a missing secret yields an empty header rather than a literal "${TOKEN}" leaking onto the wire.

func ImportCCSwitchMCP

func ImportCCSwitchMCP() (total, added, updated int, err error)

ImportCCSwitchMCP upserts cc-switch's Codex-enabled MCP servers into the active fairpeer config and saves it.

func ImportCCSwitchMCPEntries

func ImportCCSwitchMCPEntries(entries []PluginEntry) (total, added, updated int, err error)

func IsLikelyChatModel

func IsLikelyChatModel(model string) bool

IsLikelyChatModel reports whether a model ID looks like a chat/completion model rather than a specialised audio/vision/embedding model. It applies a conservative name-based heuristic — the OpenAI-compatible /models API does not return capability/modality metadata, so this is the most reliable fallback until providers add such fields.

The heuristic works in two passes:

  1. Multi-word substring check for compound terms that span separators (e.g. "text-embedding", "text-to-speech").
  2. Token-level check: the model ID is split on common separators (- _ . / :) and each token is compared against a set of known non-chat keywords.

"voice" is intentionally absent from the non-chat set because it is too broad — legitimate future chat models may include it in their name.

func IsValidSkillName

func IsValidSkillName(name string) bool

IsValidSkillName reports whether name is a usable skill identifier.

func MemoryUserDir

func MemoryUserDir() string

MemoryUserDir returns the fairpeer user config root (…/fairpeer), under which the user-global fairpeer.md and the per-project auto-memory store live. Empty when the user config dir can't be resolved, which disables user-scoped memory.

func ModelRefsProvider

func ModelRefsProvider(ref, name string) bool

ModelRefsProvider reports whether ref targets the named provider. It matches both bare provider names ("openai") and "provider/model" refs.

func NormalizeEffort

func NormalizeEffort(e *ProviderEntry, raw string) (string, error)

NormalizeEffort maps a user-supplied /effort level into the value stored in config. Empty means auto/provider default. All providers use the unified low/medium/high vocabulary. Legacy values are migrated:

  • max/xhigh → high
  • adaptive → high
  • disabled/off → low

func NormalizeLegacyDesktopProviderAccess

func NormalizeLegacyDesktopProviderAccess(c *Config)

NormalizeLegacyDesktopProviderAccess seeds the desktop provider-access list for configs written before Settings tracked explicit provider access. Callers should only use this when they know the TOML did not declare provider_access; an explicit empty list means the user removed all access entries.

func PluginAllowedByProfile

func PluginAllowedByProfile(p *Profile, pluginName string) bool

PluginAllowedByProfile reports whether pluginName is visible under profile p. An empty p.Plugins list means "all plugins allowed" (the dev default), so a profile that does not opt into plugin filtering keeps the full MCP set. When p is nil (no profile), all plugins are allowed.

func ProfileNameKey

func ProfileNameKey(name string) string

ProfileNameKey normalizes a profile identifier for comparisons. Profile names are case- and whitespace-insensitive so "Cowork" / "COWORK" / "cowork" all resolve the same. Empty stays empty (resolved to ProfileDev upstream).

func ProjectSessionDir

func ProjectSessionDir(workspaceRoot string) string

ProjectSessionDir is the per-workspace session directory the desktop sidebar lists: <config root>/projects/<slug>/sessions. Empty when either the config root or workspaceRoot doesn't resolve.

func ProjectSessionDirFor

func ProjectSessionDirFor(workspaceRoot, profile string) string

ProjectSessionDirFor returns the per-workspace session directory partitioned by profile: <config root>/projects/<slug>/<profileKey>/sessions. The default profile (empty/"dev") is backward compatible with ProjectSessionDir — it returns the un-profiled path. Empty when the workspace root doesn't resolve. Used by desktopSessionDirFor so each tab's session lands in its own profile partition and dev/cowork conversations don't mix.

func ReasoningProtocolForEntry

func ReasoningProtocolForEntry(e *ProviderEntry) string

ReasoningProtocolForEntry resolves the provider request shape for reasoning controls. Explicit per-provider config wins. With no URL-based fallback, an empty result means the provider uses standard OpenAI-compatible request shape unless it declares reasoning_protocol = "openai" explicitly.

func RenderTOML

func RenderTOML(c *Config) string

RenderTOML renders the config as annotated TOML in the `fairpeer setup` house style: comments preserved, system_prompt as a multi-line string, helpful hints. The output round-trips back through Load (see render_test.go).

func RenderTOMLForScope

func RenderTOMLForScope(c *Config, scope RenderScope) string

RenderTOMLForScope renders an annotated TOML file for a specific persistence target. User configs can carry desktop and account-level preferences; project fairpeer.toml stays focused on project behavior and intentionally excludes desktop-only preferences.

func SaveMinimalProjectAutoPlan

func SaveMinimalProjectAutoPlan(path, mode string) (string, error)

SaveMinimalProjectAutoPlan writes a new project config that only overrides [agent].auto_plan. It is intentionally minimal so toggling a project-local auto-plan preference in an otherwise unconfigured workspace does not pin default_model or providers from built-in defaults.

func SessionDir

func SessionDir() string

SessionDir is where chat sessions are persisted (one .jsonl per session). Used by `fairpeer chat --continue` / `--resume` to find the recent ones. Empty if the user config dir can't be resolved — sessions then aren't saved.

func SessionDirFor

func SessionDirFor(profile string) string

SessionDirFor returns the session directory for a given profile. The default profile (empty name, or the builtin "dev"/"default") shares the top-level <userDir>/sessions so existing --continue/--resume history stays intact; a named profile (e.g. "cowork") partitions under <userDir>/sessions/<key> so its transcripts don't mix with the default's. Returns "" when the user dir can't be resolved. boot.go uses this so --profile cowork lands in its own partition.

func SkillNameKey

func SkillNameKey(name string) string

SkillNameKey normalizes a skill identifier for config comparisons.

func SourcePath

func SourcePath() string

SourcePath returns the highest-priority config file that exists, or "" if none.

func SourcePathForRoot

func SourcePathForRoot(root string) string

SourcePathForRoot returns the highest-priority config file that exists under root, or "" if none. Equivalent to SourcePath() when root is ".".

func UserConfigPath

func UserConfigPath() string

UserConfigPath is the user-global config file (~/.config/fairpeer/config.toml), or "" when the user config dir can't be resolved.

func UserCredentialsPath

func UserCredentialsPath() string

UserCredentialsPath is the fairpeer-owned global secrets file, beside config.toml in the user config dir (e.g. ~/.config/fairpeer/credentials). It holds KEY=value lines loaded into the environment by loadDotEnv. The setup wizard writes API keys here, deliberately NOT named .env: keys never land in a project's own .env (which can't be selectively gitignored), never get committed, and resolve from any working directory. "" when the user config dir can't be resolved.

func WorkspaceSlug

func WorkspaceSlug(absPath string) string

WorkspaceSlug flattens an absolute workspace path into the directory name used under <config root>/projects.

Types

type AgentConfig

type AgentConfig struct {
	SystemPrompt     string            `toml:"system_prompt"`
	SystemPromptFile string            `toml:"system_prompt_file"`
	MaxSteps         int               `toml:"max_steps"`         // tool-call rounds per turn; 0 = unlimited
	PlannerMaxSteps  int               `toml:"planner_max_steps"` // planner read-only tool-call rounds; 0 = unlimited
	Temperature      float64           `toml:"temperature"`
	PlannerModel     string            `toml:"planner_model"`
	SubagentModel    string            `toml:"subagent_model"`
	SubagentModels   map[string]string `toml:"subagent_models"`
	SubagentEffort   string            `toml:"subagent_effort"`
	SubagentEfforts  map[string]string `toml:"subagent_efforts"`
	FastTaskModel    string            `toml:"fast_task_model"` // lightweight model for dream/distill background tasks
	// OutputStyle selects a persona/tone block folded into the system prompt at
	// startup (a built-in like "explanatory"/"learning"/"concise", or a custom
	// .fairpeer/output-styles/<name>.md). Empty = the unmodified prompt.
	OutputStyle string `toml:"output_style"`
	// AutoPlan controls whether interactive turns that look multi-step start in
	// plan mode automatically: "off" keeps plan mode manual, "on" enables the
	// approval gate. Legacy "ask" is treated as "on".
	AutoPlan string `toml:"auto_plan"`
	// AutoPlanClassifier optionally names a provider/model used to classify
	// borderline auto-plan decisions. Empty keeps the zero-cost heuristic path.
	AutoPlanClassifier string `toml:"auto_plan_classifier"`
	// Compaction window fractions: soft = notice only, compact = trigger, force = hard ceiling.
	SoftCompactRatio  float64 `toml:"soft_compact_ratio"`
	CompactRatio      float64 `toml:"compact_ratio"`
	CompactForceRatio float64 `toml:"compact_force_ratio"`
	// ContextBudgetPercent caps the effective window for compaction decisions
	// (SPEC v2 §3.6). 0/100 = full window (default, zero config). E.g. 80 =
	// compact as if the window were 80% of its real size — saves cost on input
	// pricing tiers and avoids quality drop near the window edge.
	ContextBudgetPercent int `toml:"context_budget_percent"`
}

AgentConfig configures the harness loop. PlannerModel is optional: when set to another provider's name it enables two-model collaboration, where the planner handles low-frequency planning in its own session (kept separate so each model's prompt prefix stays cache-stable; some providers do not report cache tokens). SubagentModel is the optional default for runAs=subagent skills; SubagentModels overrides it per skill name.

type BotAllowlist

type BotAllowlist struct {
	Enabled      bool     `toml:"enabled"`
	AllowAll     bool     `toml:"allow_all"`
	Mode         string   `toml:"mode"` // "open"(默认,自动加入)| "review"(需管理员审批)
	QQUsers      []string `toml:"qq_users"`
	FeishuUsers  []string `toml:"feishu_users"`
	WeixinUsers  []string `toml:"weixin_users"`
	QQGroups     []string `toml:"qq_groups"`
	FeishuGroups []string `toml:"feishu_groups"`
	WeixinGroups []string `toml:"weixin_groups"`
}

BotAllowlist 控制哪些用户可以使用 bot。

type BotConfig

type BotConfig struct {
	Enabled     bool                  `toml:"enabled"`
	Model       string                `toml:"model"` // 用于 bot 的模型名,空则用 default_model
	MaxSteps    int                   `toml:"max_steps"`
	DebounceMs  int                   `toml:"debounce_ms"` // 消息合并窗口,毫秒
	Allowlist   BotAllowlist          `toml:"allowlist"`
	QQ          QQBotConfig           `toml:"qq"`
	Feishu      FeishuBotConfig       `toml:"feishu"`
	Weixin      WeixinBotConfig       `toml:"weixin"`
	Connections []BotConnectionConfig `toml:"connections"`
}

BotConfig 控制多渠道 IM bot 消息网关。

type BotConnectionConfig

type BotConnectionConfig struct {
	ID              string                        `toml:"id"`
	Provider        string                        `toml:"provider"` // qq|feishu|weixin
	Domain          string                        `toml:"domain"`   // feishu|lark|weixin|qq
	Label           string                        `toml:"label"`
	Enabled         bool                          `toml:"enabled"`
	Status          string                        `toml:"status"` // disconnected|pending|connected|error
	Model           string                        `toml:"model"`
	WorkspaceRoot   string                        `toml:"workspace_root"`
	Credential      BotConnectionCredential       `toml:"credential"`
	SessionMappings []BotConnectionSessionMapping `toml:"session_mappings"`
	LastError       string                        `toml:"last_error"`
	CreatedAt       string                        `toml:"created_at"`
	UpdatedAt       string                        `toml:"updated_at"`
}

BotConnectionConfig is the desktop-friendly connection record for IM bot channels. It keeps install/runtime state separate from legacy per-provider knobs so the UI can expose a simple "connect first" flow while old configs keep working.

type BotConnectionCredential

type BotConnectionCredential struct {
	AppID        string `toml:"app_id"`
	AppSecretEnv string `toml:"app_secret_env"`
	AccountID    string `toml:"account_id"`
	TokenEnv     string `toml:"token_env"`
}

type BotConnectionSessionMapping

type BotConnectionSessionMapping struct {
	RemoteID      string `toml:"remote_id"`
	SessionID     string `toml:"session_id"`
	Scope         string `toml:"scope"`
	WorkspaceRoot string `toml:"workspace_root"`
	UpdatedAt     string `toml:"updated_at"`
}

type BuiltInMCPConfig

type BuiltInMCPConfig struct {
	Context7Enabled bool `toml:"context7_enabled"`
}

BuiltInMCPConfig controls which built-in MCP servers are enabled. Each server has a corresponding *_enabled boolean. Default is off for servers that require external dependencies (e.g. npx for Context7).

func (BuiltInMCPConfig) Enabled

func (c BuiltInMCPConfig) Enabled(name string) bool

Enabled reports whether the named built-in MCP server is enabled.

func (BuiltInMCPConfig) EnabledNames

func (c BuiltInMCPConfig) EnabledNames() []string

EnabledNames returns the names of all enabled built-in MCP servers.

func (*BuiltInMCPConfig) SetEnabled

func (c *BuiltInMCPConfig) SetEnabled(name string, enabled bool) bool

SetEnabled sets the enabled flag for the named built-in MCP server. Returns false if the name is unknown.

type CodegraphConfig

type CodegraphConfig struct {
	Enabled     bool   `toml:"enabled"`
	AutoInstall bool   `toml:"auto_install"`
	Path        string `toml:"path"` // local binary path (skips download)
	Tier        string `toml:"tier"`
	DownloadURL string `toml:"download_url"` // custom download base URL for air-gapped/intranet (replaces GitHub default)
}

CodegraphConfig governs the built-in CodeGraph MCP server — symbol/call-graph code intelligence (tree-sitter + SQLite) that gives the agent codegraph_* search / context / explore / trace / node tools. Enabled defaults to true so upgrades keep it for existing configs; first-run scaffolds write enabled = false so only brand-new users start without it. AutoInstall (default true) lets fairpeer fetch the CodeGraph runtime into its cache when CodeGraph is enabled but missing; set false to require an explicit `fairpeer codegraph install` (e.g. for air-gapped or headless runs). Path overrides binary resolution; empty resolves the cache, then a `codegraph` on PATH, then a bundle beside the executable. CodeGraph always starts in the background when enabled; legacy tier values are ignored and removed during config load.

func (CodegraphConfig) ResolvedTier

func (c CodegraphConfig) ResolvedTier() string

func (CodegraphConfig) ShouldAutoStart

func (c CodegraphConfig) ShouldAutoStart() bool

type Config

type Config struct {
	ConfigVersion int    `toml:"config_version"`
	DefaultModel  string `toml:"default_model"`
	Language      string `toml:"language"` // ui/model language tag (e.g. "zh"); empty = auto-detect from $LANG / $FAIRPEER_LANG
	// ReasoningLanguage steers ONLY the visible thinking/reasoning text language
	// (auto|zh|en), independent of the final-answer language. Default "auto" leaves
	// it to the provider. It is injected as a transient per-turn block, never into
	// the cache-stable system prompt prefix.
	ReasoningLanguage string              `json:"-" toml:"reasoning_language"` // auto|zh|en; empty = auto
	UI                UIConfig            `toml:"ui"`
	Desktop           DesktopConfig       `toml:"desktop"`
	Notifications     NotificationsConfig `toml:"notifications"`
	Agent             AgentConfig         `toml:"agent"`
	Providers         []ProviderEntry     `toml:"providers"`
	Tools             ToolsConfig         `toml:"tools"`
	Permissions       PermissionsConfig   `toml:"permissions"`
	Sandbox           SandboxConfig       `toml:"sandbox"`
	Network           NetworkConfig       `toml:"network"`
	Plugins           []PluginEntry       `toml:"plugins"`
	Skills            SkillsConfig        `toml:"skills"`
	Codegraph         CodegraphConfig     `toml:"codegraph"`
	BuiltInMCP        BuiltInMCPConfig    `toml:"builtin_mcp"`
	Dream             DreamConfig         `toml:"dream"`
	Statusline        StatuslineConfig    `toml:"statusline"`
	LSP               LSPConfig           `toml:"lsp"`
	Bot               BotConfig           `toml:"bot"`
	// Cowork holds coWork (office) profile settings — currently just the browser
	// path override. Empty means auto-detect; a non-empty path is tried first
	// (and the user is guided to set it when no browser is found).
	Cowork CoworkConfig `toml:"cowork"`
	// LLM holds the global request budget (rate limiting) applied to all
	// providers. RPM=0 (the default) disables limiting for backward compat.
	LLM LLMConfig `toml:"llm"`
	// Profiles holds optional [[profiles]] entries that override the built-in
	// dev/cowork profiles by name. A name collision with a builtin replaces it,
	// so users can customise a profile's model/prompt/skills without code. Empty
	// means only the builtins are available (dev + cowork).
	Profiles []Profile `toml:"profiles"`
}

Config is fairpeer's runtime configuration.

func Default

func Default() *Config

Default returns the built-in default configuration. No provider is preset — FairPeer is provider-agnostic, so the user configures their own via the CLI setup wizard (fairpeer chat/run) or the desktop onboarding/settings panel. An empty DefaultModel + empty Providers means the first run enters the setup wizard (CLI) or the onboarding overlay (desktop).

func Load

func Load() (*Config, error)

Load builds the configuration: defaults, then user config, then project config, then MCP servers from Claude Code's .mcp.json, then (lowest priority) the v0.x ~/.fairpeer/config.json's mcpServers. A .env in the working directory is loaded first so api_key_env can resolve.

func LoadForEdit

func LoadForEdit(path string) *Config

LoadForEdit returns a config to seed the `fairpeer setup` wizard when reconfiguring: the built-in defaults with the file at path (if present) decoded on top, so a reconfigure preserves the user's existing providers and agent settings instead of resetting to defaults. .env is loaded so api_key_env resolution works while the wizard decides which keys are still missing.

func LoadForRoot

func LoadForRoot(root string) (*Config, error)

LoadForRoot builds the configuration with project files resolved from root instead of the current working directory. When root is "" or ".", it behaves like Load(). This is the workspace-aware entry point: desktop tabs use it so each project's fairpeer.toml + .env + .mcp.json are resolved independently without changing the process cwd.

func (*Config) AddPermissionRule

func (c *Config) AddPermissionRule(list, rule string) error

AddPermissionRule appends a rule ("ToolName" or "ToolName(glob)") to the allow / ask / deny list. The rule is validated with the same parser the gate uses, and a duplicate is a no-op so a UI can call it idempotently.

func (*Config) AddSkillPath

func (c *Config) AddSkillPath(path string) error

AddSkillPath appends a custom skill root, deduping by its expanded absolute path while preserving the caller's original spelling in the config file.

func (*Config) AutoStartPlugins

func (c *Config) AutoStartPlugins() []PluginEntry

func (*Config) BashMode

func (c *Config) BashMode() string

BashMode normalises the bash-sandbox mode: only an explicit "off" disables it; empty or any other value resolves to "enforce", so the sandbox is on by default and fails safe.

func (*Config) BashTimeoutSeconds

func (c *Config) BashTimeoutSeconds() int

BashTimeoutSeconds returns the foreground bash timeout in seconds. An omitted config keeps the historical 120s safety cap, explicit 0 disables the tool-local cap, and positive values set a custom cap. Negative values fall back to the default so a typo cannot silently remove the safety net.

func (*Config) ClearPluginAuthentication

func (c *Config) ClearPluginAuthentication(name string) (PluginEntry, bool, error)

ClearPluginAuthentication removes locally stored auth-like material for one MCP server while keeping the server entry itself. It intentionally leaves non-auth config (command, URL host/path, ordinary env/header keys, tier) alone.

func (*Config) DesktopCheckUpdates

func (c *Config) DesktopCheckUpdates() bool

DesktopCheckUpdates reports whether the desktop should check for updates on startup. Missing configs default to true so existing users keep update notices.

func (*Config) DesktopCloseBehavior

func (c *Config) DesktopCloseBehavior() string

DesktopCloseBehavior normalizes the desktop close-window preference. It falls back to the legacy ui.close_behavior value for configs written before [desktop] existed.

func (*Config) DesktopDisplayMode

func (c *Config) DesktopDisplayMode() string

DesktopDisplayMode normalizes the transcript display mode. Default is "minimal" (collapsed model-generated intermediate items).

func (*Config) DesktopLanguage

func (c *Config) DesktopLanguage() string

DesktopLanguage normalizes the desktop UI language. Empty means auto-detect from the browser/OS locale; it deliberately does not read top-level language, which is used by the CLI/model-facing runtime.

func (*Config) DesktopMetrics

func (c *Config) DesktopMetrics() bool

DesktopMetrics reports whether the desktop sends opt-in aggregate agent metrics — anonymous (signal, bucket) counters, never content. Default off.

func (*Config) DesktopTelemetry

func (c *Config) DesktopTelemetry() bool

DesktopTelemetry reports whether the desktop sends the anonymous launch ping. It carries no conversation, key, or file data — see desktop/README.md.

func (*Config) DesktopTheme

func (c *Config) DesktopTheme() string

DesktopTheme normalizes desktop.theme. New desktop users default to the light graphite product look; an explicit auto/light/dark is preserved.

func (*Config) DesktopThemeStyle

func (c *Config) DesktopThemeStyle() string

DesktopThemeStyle normalizes desktop.theme_style. Empty means the frontend chooses the default style for the resolved desktop theme.

func (*Config) DisabledSkillNames

func (c *Config) DisabledSkillNames() []string

DisabledSkillNames returns valid disabled skill identifiers, preserving the first spelling and dropping duplicates/empty entries.

func (*Config) ExcludeSkillPath

func (c *Config) ExcludeSkillPath(path string) error

ExcludeSkillPath hides any skill discovery root matching path. This is used by UI "remove source" actions for convention roots that are not stored in paths.

func (*Config) IsProfileKnown

func (c *Config) IsProfileKnown(name string) bool

IsProfileKnown reports whether name resolves to a profile (builtin or configured). Empty returns true (resolves to dev).

func (*Config) IsSkillDisabled

func (c *Config) IsSkillDisabled(name string) bool

IsSkillDisabled reports whether name is configured as disabled.

func (*Config) NetworkProxyMode

func (c *Config) NetworkProxyMode() string

NetworkProxyMode normalizes network.proxy_mode to a known value.

func (*Config) NetworkProxySpec

func (c *Config) NetworkProxySpec() netclient.ProxySpec

NetworkProxySpec returns the expanded proxy settings used by netclient.

func (*Config) Provider

func (c *Config) Provider(name string) (*ProviderEntry, bool)

Provider returns the named provider entry.

func (*Config) ReadRoots

func (c *Config) ReadRoots() []string

ReadRoots returns the directories read_file/grep are confined to, with ${VAR} expanded. Empty (the default) means reads are unconfined — the safe default that preserves the agent's ability to read system files. boot passes this to builtin.ConfineReaders only when non-empty. Audit A7.

func (*Config) RemovePermissionRule

func (c *Config) RemovePermissionRule(list, rule string) (bool, error)

RemovePermissionRule drops the first exact match of rule from the named list, reporting whether anything was removed.

func (*Config) RemovePlugin

func (c *Config) RemovePlugin(name string) bool

RemovePlugin deletes the named MCP server, reporting whether it was present.

func (*Config) RemoveProvider

func (c *Config) RemoveProvider(name string) error

RemoveProvider deletes the named provider. References to the removed provider are migrated to the first remaining configured provider when possible. The default model is required, so removal is refused when no fallback exists; optional planner/subagent refs are cleared instead of being left dangling.

func (*Config) RemoveSkillPath

func (c *Config) RemoveSkillPath(path string) (bool, error)

RemoveSkillPath removes the first custom skill root matching path after expansion and path cleaning. It reports whether anything changed.

func (*Config) ResolveModel

func (c *Config) ResolveModel(ref string) (*ProviderEntry, bool)

ResolveModel resolves a model reference to a provider entry whose Model is the selected model string (a copy, so the config's lists stay intact). It accepts:

  • "provider/model" — that exact model under that provider;
  • a provider name — the provider's default model;
  • a bare model name — the (first) provider that lists it.

The returned entry is ready to build a provider from (NewProvider reads .Model), so a single "vendor with many models" entry yields one instance per model without duplicating base_url/api_key_env. Single-`model` entries still resolve by provider name, keeping older configs working unchanged.

func (*Config) ResolveModelWithFallback

func (c *Config) ResolveModelWithFallback(ref string) (resolvedRef string, fallback bool, ok bool)

ResolveModelWithFallback resolves a model reference to the canonical "provider/model" form used by the desktop runtime. If ref is stale or empty, it tries the user's configured default_model before falling back to the first configured provider — so preference isn't overwritten by iteration order.

func (*Config) ResolveProfile

func (c *Config) ResolveProfile(name string) (*Profile, error)

ResolveProfile returns the effective profile for name, or an error if the name is unknown. The builtin floor is merged with the config's [[profiles]] entries (config wins on name collision). Empty name resolves to ProfileDev so callers that never set a profile get unprofiled behaviour. The returned *Profile is a copy of the merged entry; mutating it does not affect the Config.

func (*Config) ResolveSystemPrompt

func (c *Config) ResolveSystemPrompt() (string, error)

ResolveSystemPrompt returns the system prompt, reading system_prompt_file if set.

func (*Config) RestoreSkillPath

func (c *Config) RestoreSkillPath(path string) error

RestoreSkillPath removes a pseudo-deleted skill source from excluded_paths.

func (*Config) Save

func (c *Config) Save() error

Save writes the configuration back to the file it was loaded from (SourcePath), or to ./fairpeer.toml when none exists yet — the conventional project-local target a fresh GUI session would create.

func (*Config) SaveForRoot

func (c *Config) SaveForRoot(root string) error

SaveForRoot saves the config to root's fairpeer.toml, falling back to the user's global config when root has no existing fairpeer.toml.

func (*Config) SaveTo

func (c *Config) SaveTo(path string) error

SaveTo writes the configuration to path as annotated TOML, atomically: it writes a sibling temp file then renames, so a crash mid-write can't leave a half-written fairpeer.toml that fails to parse on next load. Parent directories are created as needed.

func (*Config) SaveToScope

func (c *Config) SaveToScope(path string, scope RenderScope) error

func (*Config) SetAutoPlan

func (c *Config) SetAutoPlan(mode string) error

SetAutoPlan sets the interactive auto-plan gate. "off" keeps plan mode manual; "on" opts into automatic read-only planning for complex-looking turns. "ask" is accepted as a legacy synonym for "on" but is never written back.

func (*Config) SetDefaultModel

func (c *Config) SetDefaultModel(name string) error

SetDefaultModel points default_model at an existing model. It accepts both forms used by the runtime resolver:

  • "provider" — the provider's own default model;
  • "provider/model" — that specific model under that provider.

Either is rejected when the target does not exist, so a UI can't strand the config on a model that doesn't exist.

func (*Config) SetDesktopAppearance

func (c *Config) SetDesktopAppearance(theme, style string) error

SetDesktopAppearance sets desktop-only theme preferences. It must not affect CLI theme settings or provider-visible request data.

func (*Config) SetDesktopCheckUpdates

func (c *Config) SetDesktopCheckUpdates(enabled bool) error

SetDesktopCheckUpdates sets whether the desktop app checks for updates on startup. Manual checks remain available in Settings regardless of this value.

func (*Config) SetDesktopCloseBehavior

func (c *Config) SetDesktopCloseBehavior(mode string) error

SetDesktopCloseBehavior sets the desktop close-window preference. It is intentionally UI-only and must not affect model prompts or provider-visible request data.

func (*Config) SetDesktopDisplayMode

func (c *Config) SetDesktopDisplayMode(mode string) error

SetDesktopDisplayMode sets the transcript display mode. UI-only.

func (*Config) SetDesktopLanguage

func (c *Config) SetDesktopLanguage(lang string) error

SetDesktopLanguage pins the desktop UI language. It intentionally does not modify Config.Language, which is used by the CLI/model-facing runtime.

func (*Config) SetDesktopMetrics

func (c *Config) SetDesktopMetrics(enabled bool) error

SetDesktopMetrics sets whether the desktop sends opt-in aggregate agent metrics.

func (*Config) SetDesktopTelemetry

func (c *Config) SetDesktopTelemetry(enabled bool) error

SetDesktopTelemetry sets whether the desktop sends the anonymous launch ping.

func (*Config) SetDreamEnabled

func (c *Config) SetDreamEnabled(enabled bool)

SetDreamEnabled toggles the background self-evolution master switch. When disabled, neither Dream nor Distill spawns automatically or via manual trigger.

func (*Config) SetDreamIntervals

func (c *Config) SetDreamIntervals(dreamDays, distillDays int) error

SetDreamIntervals configures the Dream and Distill automatic-run cadence in days. A non-positive value is rejected (use the package defaults by leaving the field at 0 in TOML, not by passing <=0 here). Both intervals must be >= 1.

func (*Config) SetExpandThinking

func (c *Config) SetExpandThinking(on bool) error

SetExpandThinking sets whether the desktop reasoning/thinking section is expanded by default. It is desktop-only and must not affect CLI output or provider-visible request data.

func (*Config) SetLanguage

func (c *Config) SetLanguage(lang string) error

SetLanguage pins the CLI UI/model language; empty/auto clears the override so runtime detection falls back to FAIRPEER_LANG / locale.

func (*Config) SetNetwork

func (c *Config) SetNetwork(n NetworkConfig) error

SetNetwork updates ordinary outbound network proxy settings. Invalid custom proxy settings are rejected here so the desktop panel cannot save a config that would break provider startup.

func (*Config) SetPermissionMode

func (c *Config) SetPermissionMode(mode string) error

SetPermissionMode sets the writer-fallback mode. Accepts "ask", "allow", or "deny" (case-insensitive); anything else errors rather than silently defaulting, so a UI surfaces a typo instead of installing a surprising mode.

func (*Config) SetPlannerModel

func (c *Config) SetPlannerModel(name string) error

SetPlannerModel sets (or, with "", clears) agent.planner_model for two-model collaboration. A non-empty name must be a configured provider.

func (*Config) SetProviderEffort

func (c *Config) SetProviderEffort(name, effort string) error

SetProviderEffort updates a provider's provider-specific thinking effort knob.

func (*Config) SetProviderThinking

func (c *Config) SetProviderThinking(name, thinking string) error

SetProviderThinking updates a provider's provider-specific thinking mode knob.

func (*Config) SetShowReasoning

func (c *Config) SetShowReasoning(on bool) error

SetShowReasoning sets the CLI's default verbose-reasoning preference. When true, thinking text is shown in the chat TUI on startup; when false (the default), it stays collapsed until the user toggles it with Ctrl+O or /verbose.

func (*Config) SetSkillEnabled

func (c *Config) SetSkillEnabled(name string, enabled bool) error

SetSkillEnabled persists a per-skill enable/disable preference. Skills are enabled by default; disabling records the name, enabling removes it.

func (*Config) SetUICloseBehavior

func (c *Config) SetUICloseBehavior(mode string) error

SetUICloseBehavior is kept for callers compiled against the old edit API.

func (*Config) SetUIShortcutLayout

func (c *Config) SetUIShortcutLayout(layout string) error

historical behavior; "desktop" enables the two-axis desktop-style shortcuts.

func (*Config) SkillCustomPaths

func (c *Config) SkillCustomPaths() []string

SkillCustomPaths returns the configured custom skill roots with ${VAR} expanded; empty entries are dropped.

func (*Config) SkillExcludedPaths

func (c *Config) SkillExcludedPaths() []string

SkillExcludedPaths returns configured skill roots that should be hidden from discovery, with ${VAR} expanded and empty entries dropped.

func (*Config) SkillMaxDepth

func (c *Config) SkillMaxDepth() int

SkillMaxDepth bounds nested skill discovery. Depth 3 favors bundled skill packs while Store keeps nested markdown safe by requiring descriptions.

func (*Config) UICloseBehavior

func (c *Config) UICloseBehavior() string

UICloseBehavior is the legacy name for DesktopCloseBehavior.

func (*Config) UIShortcutLayout

func (c *Config) UIShortcutLayout() string

UIShortcutLayout normalizes the legacy CLI shortcut layout setting. It is kept for compatibility; Shift+Tab toggles Plan and Ctrl+Y toggles YOLO in both layouts.

func (*Config) UITheme

func (c *Config) UITheme() string

UITheme normalizes ui.theme (dark/light/auto). Empty or unrecognized falls back to "auto", which follows the OS shell preference.

func (*Config) UIThemeStyle

func (c *Config) UIThemeStyle() string

UIThemeStyle normalizes ui.theme_style. Empty means "pick the default style for the resolved light/dark shell".

func (*Config) UpsertPlugin

func (c *Config) UpsertPlugin(e PluginEntry) error

UpsertPlugin adds e, or replaces an MCP server with the same name (preserving position). The transport-specific required fields are validated: stdio needs a command, http/sse need a url.

func (*Config) UpsertProvider

func (c *Config) UpsertProvider(e ProviderEntry) error

UpsertProvider adds e, or replaces an existing provider with the same name (preserving its position). Required fields (name, kind, base_url, model/models) are validated; whether the kind is actually registered and the key resolves is checked later by provider.New / Validate, which give actionable errors.

func (*Config) Validate

func (c *Config) Validate(model string) error

Validate checks that the selected model's provider is usable.

func (*Config) WriteFile

func (c *Config) WriteFile(path string) error

WriteFile writes the configuration to path as annotated TOML.

func (*Config) WriteRoots

func (c *Config) WriteRoots() []string

WriteRoots returns the directories file-writer tools may modify: the workspace root (defaulting to the current working directory when unset) plus any AllowWrite extras, with ${VAR} expanded. The roots are returned as given (relative or absolute); the confiner resolves them to absolute, symlink-free paths. The result is always non-empty, so confinement is on by default.

func (*Config) WriteRootsForRoot

func (c *Config) WriteRootsForRoot(fallbackRoot string) []string

WriteRootsForRoot is like WriteRoots but falls back to fallbackRoot when the config doesn't explicitly set a workspace_root. Desktop tabs pass their project root here so tool confinement is correct without changing cwd.

type CoworkConfig

type CoworkConfig struct {
	BrowserPath string `toml:"browser_path"` // absolute path to a Chromium-based browser exe; empty = auto-detect
	// BrowserHeadless controls whether the driven browser runs headless. Default
	// false (headed/visible) — a visible browser behaves closer to a human user,
	// keeps login state in a persistent profile, and avoids the rendering
	// quirks headless has on JS-heavy/anti-bot sites (e.g. GitHub's challenge
	// page). Set true for servers/CI where there is no display.
	BrowserHeadless bool `toml:"browser_headless"`
	// BrowserUserDataDir gives the driven browser a persistent user-data
	// directory. Empty = a fresh temp profile per launch (state is lost on
	// restart). Set to a fixed path to keep cookies/login across sessions —
	// essential for sites that require sign-in, and it reduces the "verify you
	// are human" friction on revisit.
	BrowserUserDataDir string `toml:"browser_user_data_dir"`
	// PPTActiveTemplate is the id of the active PPT template (from the templates
	// dir <user-config>/fairpeer/ppt-templates/<id>.json). When set, the ppt-wizard
	// skill generates decks from that template: it opens the template's master_file
	// in WPS (if any) and places content at the template's pre-defined layout
	// coordinates, so most slides don't need per-step VLM perception. Empty = no
	// template, the CUA builds from a blank deck.
	PPTActiveTemplate string `toml:"ppt_active_template"`
	// PPTMode selects how the ppt-wizard skill builds decks. "fast" (default
	// when empty) generates in one pass with no rework; "validate" generates,
	// then checks and reworks on issues. The desktop cowork settings panel
	// exposes this as a dropdown; PPTActiveTemplate still selects the template
	// used in either mode.
	PPTMode string `toml:"ppt_mode"`
	// SMTP configures outbound email (email_send). All fields required to enable
	// sending; empty SMTPHost disables email_send (it returns a config error).
	SMTP SMTPConfig `toml:"smtp"`
	// RAGEnabled is the master switch for the knowledge base (RAG). nil/unset =
	// enabled (the historical default, backward compatible). Set to false to
	// fully disable the knowledge base: no auto-injection into messages, the
	// rag_search/rag_import/... tools are not registered, and expert teams skip
	// knowledge-base context. This is distinct from EmbeddingModel (which only
	// toggles semantic reranking on top of FTS5) — RAGEnabled governs whether RAG
	// runs at all.
	RAGEnabled *bool `toml:"rag_enabled"`
	// EmbeddingModel enables semantic RAG reranking. When set to a provider model
	// ref that supports embeddings, rag_search computes a query embedding and
	// reranks FTS5 hits by cosine similarity. Empty = FTS5-only (the default,
	// works offline). Set to a real embedding model (e.g. a provider with kind
	// "embedding") to upgrade RAG to hybrid.
	EmbeddingModel string `toml:"embedding_model"`
	// VLMBackend is legacy: VLM now always uses the provider multimodal chat path
	// (base64 image_url content parts). Kept for TOML backward-compat but ignored.
	// Use VLMModel / ScreenshotVLMModel to pick the vision model.
	VLMBackend string `toml:"vlm_backend"`
	// VLMModel is the provider model ref for image recognition (screen_perceive).
	// E.g. "<provider>/<model>". Must be vision-capable (provider vision=true).
	VLMModel string `toml:"vlm_model"`
	// IMAP configures inbound email (email_read/search). Empty Host = read tools
	// return "not configured". Reading uses go-imap + go-message (protocol-level
	// correct: full SEARCH, RFC 2047 header decoding, multipart MIME).
	IMAP IMAPConfig `toml:"imap"`
	// EmailAccounts holds the mailboxes FairPeer can talk to. At load time
	// normalizeEmailAccounts folds the legacy single [cowork.smtp]/[cowork.imap]
	// pair above into EmailAccounts[0] when this slice is empty, so existing
	// single-account configs keep working unchanged; new configs may use either
	// form. Tools select an account by Name; Default (or [0]) is the fallback.
	EmailAccounts []EmailAccount `toml:"email_accounts"`
	// ExtractModel is the LLM used by the RAG deep-extraction pipeline (turns
	// imported documents into a structured entity/relation graph). Empty = fall
	// back to the active profile's main model. Pair with ExtractInterval /
	// ExtractConcurrency to tune request cadence; the pipeline is conservative
	// by default (1 concurrent chunk, 3s between chunks) to avoid rate limits.
	ExtractModel string `toml:"extract_model"`
	// ExtractInterval is the pause between chunk extractions (default "3s").
	// Raise it on rate-limited endpoints; lower on generous local models.
	ExtractInterval string `toml:"extract_interval"`
	// ExtractConcurrency is how many chunks extract in parallel (default 1).
	// Keep low to avoid tripping rate limits — extraction is a background task
	// where throughput matters less than "no errors".
	ExtractConcurrency int `toml:"extract_concurrency"`

	// ScreenshotEnabled turns on the global-hotkey screenshot-to-VLM feature.
	// When true, pressing ScreenshotHotkey anywhere (even when FairPeer is in
	// the background) captures the screen, sends it to ScreenshotVLMModel for
	// recognition, and replies via IM bot + in-app toast. Default false — the
	// user opts in via the cowork settings tab.
	ScreenshotEnabled bool `toml:"screenshot_enabled"`
	// ScreenshotHotkey is the global hotkey combination (e.g. "Ctrl+Shift+Alt+W").
	// Detected via GetAsyncKeyState polling so it fires even when FairPeer isn't
	// focused. Default "Ctrl+Shift+Alt+W".
	ScreenshotHotkey string `toml:"screenshot_hotkey"`
	// ScreenshotVLMModel is the model used for screenshot recognition.
	// This is the SINGLE place all image-recognition config lives —
	// set it once in the cowork settings page.
	ScreenshotVLMModel string `toml:"screenshot_vlm_model"`
	// ScreenshotPrompt is the user prompt sent with the screenshot image to the
	// VLM model. Users can customize this to change the solving behavior (e.g.
	// focus on specific subjects, require verification, etc.). Empty means use
	// the built-in default.
	ScreenshotPrompt string `toml:"screenshot_prompt"`

	// EStopHotkey is the global EMERGENCY-STOP hotkey for coWork desktop
	// automation. Pressing it anywhere (even with FairPeer minimized) cancels
	// the in-flight turn on the active tab — the kill switch for screen_* tools,
	// whose clicks/typing are irreversible. Registered via Win32 RegisterHotKey
	// like the screenshot hotkey. Default "Ctrl+Shift+Pause". Set to "off" to
	// disable the feature entirely.
	EStopHotkey string `toml:"estop_hotkey"`
	// HEPort is the port for the Hyper-Extract Python server. Default 0 means
	// use the built-in default (18900).
	HEPort int `toml:"he_port"`
	// BrowserUseEnabled controls whether the browser-use autonomous-browsing
	// sidecar is wired up. When false (the zero-value DEFAULT), browser_auto
	// returns a clear "disabled" error and no Python sidecar is started. This
	// is intentionally opt-in: the sidecar needs the browser-use Python package
	// installed (and a provider client), so users who haven't set that up are
	// not bothered by startup failures. Set browser_use_enabled = true once
	// the environment is ready.
	BrowserUseEnabled bool `toml:"browser_use_enabled"`
	// BrowserUsePython overrides the Python interpreter used to run the
	// browser-use sidecar. Empty = "python" (Windows) / "python3" (other). In
	// the packaged build this resolves to the bundled runtime's python.exe.
	BrowserUsePython string `toml:"browser_use_python"`
	// BrowserUsePort is the port for the browser-use sidecar. Default 0 means
	// use the built-in default (18901, distinct from HE's 18900).
	BrowserUsePort int `toml:"browser_use_port"`
	// BrowserUseModel is the provider model ref the sidecar uses for the
	// agentic loop (e.g. "<provider>/<model>"). Empty = fall back to VLMModel,
	// then the main agent model. A strong vision-capable model is strongly
	// recommended — the loop reads screenshots/accessibility trees.
	BrowserUseModel string `toml:"browser_use_model"`
	// BrowserUseMaxSteps caps the agentic loop. Default 0 means let the sidecar
	// pick a sensible bound. Set lower for cheaper/faster runs, higher for
	// complex multi-page tasks.
	BrowserUseMaxSteps int `toml:"browser_use_max_steps"`
	// FastLLMBaseDomain overrides the base URL for direct /chat/completions calls
	// made by the scheduler time-parser and RAG ask (legacy path; Phase 3 will
	// route these through the resolved fast-task provider instead). Empty = the
	// built-in default.
	FastLLMBaseDomain string `toml:"fast_llm_base_domain"`
}

CoworkConfig holds coWork (office) profile settings: browser path, PPT generation, email (SMTP/IMAP), RAG/knowledge base, screenshot recognition, hotkeys, and the Hyper-Extract service. BrowserPath overrides the auto-detected Chromium-based browser; when empty, browser_* tools probe the standard Chrome/Edge/Brave install locations and fall back to CHROME_PATH. Users set this when the browser isn't in a standard location — the agent surfaces a clear error guiding them to fill [cowork] browser_path.

func (CoworkConfig) DefaultEmailAccount

func (c CoworkConfig) DefaultEmailAccount() (EmailAccount, bool)

DefaultEmailAccount returns the account to use when no name is given: the one flagged Default, else the first, else a zero account with ok=false.

func (CoworkConfig) EmailAccountByName

func (c CoworkConfig) EmailAccountByName(name string) (EmailAccount, bool)

EmailAccountByName returns the account whose Name matches (case-insensitive), or the default account when name is empty. ok=false when the name is non-empty and unknown, or when there are no accounts at all.

func (CoworkConfig) RAGEnabledOrDefault

func (c CoworkConfig) RAGEnabledOrDefault() bool

RAGEnabledOrDefault reports whether the knowledge base (RAG) is enabled. A nil RAGEnabled means the user never set it → enabled (backward compatible). Only an explicit false disables RAG. Callers that gate RAG behaviour should use this rather than dereferencing the pointer directly.

type DesktopConfig

type DesktopConfig struct {
	Language       string   `toml:"language"`        // auto|en|zh; empty/auto = browser/OS auto-detect
	Theme          string   `toml:"theme"`           // auto|dark|light; empty resolves to dark
	ThemeStyle     string   `toml:"theme_style"`     // graphite|aurora|slate|carbon|nocturne|amber and legacy aliases
	CloseBehavior  string   `toml:"close_behavior"`  // quit|background; desktop window close behavior
	DisplayMode    string   `toml:"display_mode"`    // standard|compact|minimal; transcript display mode
	CheckUpdates   *bool    `toml:"check_updates"`   // startup update checks; nil keeps the default enabled
	Telemetry      *bool    `toml:"telemetry"`       // anonymous launch ping (install id + version + OS); nil keeps the default enabled
	Metrics        *bool    `toml:"metrics"`         // opt-in aggregate agent metrics (anonymous signal/bucket counts; no content); nil = disabled
	ProviderAccess []string `toml:"provider_access"` // desktop-only list of provider entries shown in Settings > Model > Access
	ExpandThinking bool     `toml:"expand_thinking"` // true = show reasoning text expanded by default; false = collapsed
}

DesktopConfig controls desktop-only UI preferences. It is intentionally separate from top-level language and [ui] so desktop choices do not affect CLI language, terminal colours, or provider-visible prompt/request data.

type DreamConfig

type DreamConfig struct {
	Enabled         bool `toml:"enabled"`          // master switch; false disables both background agents
	DreamInterval   int  `toml:"dream_interval"`   // days between automatic Dream runs; 0 = default 7
	DistillInterval int  `toml:"distill_interval"` // days between automatic Distill runs; 0 = default 30
	SkillColdDays   int  `toml:"skill_cold_days"`  // days a skill is unused before cold-retirement; 0 = default 90
	IdleMinutes     int  `toml:"idle_minutes"`     // minutes of user inactivity before a Dream run may fire; 0 = default 10
}

DreamConfig controls the background self-evolution agents: Dream consolidates session knowledge into project memory, Distill extracts repeated workflows into reusable skills. Intervals are in days; a value <= 0 falls back to the default so a partially-specified [dream] section still behaves sanely.

func (DreamConfig) DistillIntervalDays

func (d DreamConfig) DistillIntervalDays() int

DistillIntervalDays returns the effective Distill cadence in days.

func (DreamConfig) DreamIntervalDays

func (d DreamConfig) DreamIntervalDays() int

DreamIntervalDays returns the effective Dream cadence in days, applying the default when the configured value is non-positive.

func (DreamConfig) IdleMinutesEffective

func (d DreamConfig) IdleMinutesEffective() int

IdleMinutesEffective returns the effective user-inactivity threshold in minutes before an idle Dream run may fire, applying the default when the configured value is non-positive. A negative value disables idle triggering entirely (Dream then only runs via manual trigger).

func (DreamConfig) SkillColdDaysEffective

func (d DreamConfig) SkillColdDaysEffective() int

SkillColdDaysEffective returns the effective skill-cold threshold in days, applying the default when the configured value is non-positive.

type EffortCapability

type EffortCapability struct {
	Supported bool
	Levels    []string
	Default   string
}

EffortCapability describes the abstract effort levels a provider/model can set through the /effort command.

func EffortCapabilityForEntry

func EffortCapabilityForEntry(e *ProviderEntry) EffortCapability

EffortCapabilityForEntry returns the user-facing /effort levels for a resolved provider entry. Provider implementations still decide how a stored effort is serialized into requests.

func UnifiedEffortCapability

func UnifiedEffortCapability() EffortCapability

UnifiedEffortCapability returns the standard effort capability for any provider that supports reasoning. This is the Claude Code-style approach: a simple low/medium/high vocabulary that works across all providers.

type EmailAccount

type EmailAccount struct {
	Name    string     `toml:"name"`    // stable handle tools/scheduler address (e.g. "personal-139")
	Default bool       `toml:"default"` // used when a tool omits an account name
	SMTP    SMTPConfig `toml:"smtp"`
	IMAP    IMAPConfig `toml:"imap"`
}

EmailAccount bundles one mailbox's inbound (IMAP) and outbound (SMTP) settings under a user-chosen name, so FairPeer can talk to multiple mailboxes at once (e.g. a personal 139 box and a work CMCC box). Tools/scheduler select an account by Name; the one flagged Default (or else the first) is used when the caller omits a name.

type FeishuBotConfig

type FeishuBotConfig struct {
	Enabled           bool   `toml:"enabled"`
	Domain            string `toml:"domain"` // feishu(默认)| lark
	AppID             string `toml:"app_id"`
	AppSecretEnv      string `toml:"app_secret_env"`     // 如 FEISHU_BOT_APP_SECRET
	VerificationToken string `toml:"verification_token"` // 事件订阅验证 token
	Mode              string `toml:"mode"`               // webhook(默认)| websocket
	WebhookPort       int    `toml:"webhook_port"`       // webhook 模式端口
	RequireMention    bool   `toml:"require_mention"`
}

FeishuBotConfig 飞书自建应用 Bot 配置。

type IMAPConfig

type IMAPConfig struct {
	Host          string `toml:"host"`            // IMAP server host, e.g. imap.example.com
	Port          int    `toml:"port"`            // IMAP port (993 for implicit TLS, 143 for STARTTLS/plain)
	Username      string `toml:"username"`        // mailbox login
	PasswordEnv   string `toml:"password_env"`    // env var holding the password
	SkipTLSVerify bool   `toml:"skip_tls_verify"` // opt in to skip TLS cert verification (self-signed/corporate CAs). Verification stays ON unless this is true.
}

IMAPConfig holds inbound mail server settings for email_read/search.

type LLMConfig

type LLMConfig struct {
	RPM         int `toml:"rpm"`          // max requests/minute per API key (0 = unlimited)
	TPM         int `toml:"tpm"`          // max tokens/minute (0 = unlimited; reserved, not enforced yet)
	ReserveMain int `toml:"reserve_main"` // requests reserved for main-agent priority (default 2)
}

LLMConfig holds the global LLM request budget (rate limiting). It applies across ALL providers via a decorator, so main-agent + subagent + RAG extraction + IM bot responses all share the same per-API-key RPM quota.

RPM reflects the user's real API-key rate limit (default 60/min; higher tiers or other providers may allow more). Leave at 0 to disable rate limiting entirely (unlimited, backward-compatible).

ReserveMain keeps main-agent requests always answerable even when background tasks (expert teams, extraction) have consumed most of the per-minute quota: background requests wait for the next window when remaining <= reserve.

type LSPConfig

type LSPConfig struct {
	Enabled bool                 `toml:"enabled"`
	Servers map[string]LSPServer `toml:"servers"`
}

LSPConfig governs the optional Language Server Protocol tools (lsp_definition, lsp_references, lsp_hover, lsp_diagnostics). Enabled defaults to true; the servers themselves are never bundled — each resolves on PATH and the tool returns an install hint when it is missing, so the capability is dormant until the user installs a server. Servers overrides or extends the built-in language → server map, keyed by language id (e.g. "go", "rust", "python").

type LSPServer

type LSPServer struct {
	Command     string            `toml:"command"`
	Args        []string          `toml:"args"`
	Env         map[string]string `toml:"env"`
	LanguageID  string            `toml:"language_id"`
	Extensions  []string          `toml:"extensions"`
	InstallHint string            `toml:"install_hint"`
}

LSPServer overrides a built-in language's server or, when keyed by a new language, adds one. An empty field falls back to the built-in default for that language; Extensions is required when adding a language the built-ins don't cover (e.g. ".ex" for Elixir) so files route to it.

type MCPImportCandidate

type MCPImportCandidate struct {
	Entry       PluginEntry
	Recommended bool
	Reasons     []string
}

func LoadCCSwitchMCPCandidates

func LoadCCSwitchMCPCandidates() ([]MCPImportCandidate, error)

type MigrationResult

type MigrationResult struct {
	From     string
	To       string
	KeyToEnv bool
	Plugins  int
	Warnings []string
}

MigrationResult summarizes a one-time legacy import for the boot-time notice.

func MigrateLegacyIfNeeded

func MigrateLegacyIfNeeded() (*MigrationResult, error)

MigrateLegacyIfNeeded performs a one-time, non-destructive import of older installs into the current user config when the latter does not exist yet. It checks v1-era TOML first, then v0.5/v0.x ~/.fairpeer/config.json, and never modifies or deletes the legacy files. Returns nil when there is nothing to migrate, or when the current user config already exists.

func (*MigrationResult) Notice

func (r *MigrationResult) Notice() string

type NetworkConfig

type NetworkConfig struct {
	// ProxyMode is "auto" (default; environment proxy for now), "env", "custom",
	// or "off". auto leaves room for OS proxy detection later without changing the
	// config shape.
	ProxyMode string `toml:"proxy_mode"`
	// ProxyURL is an advanced custom override such as "socks5://127.0.0.1:7890".
	// When set and proxy_mode = "custom", it wins over the structured proxy table.
	ProxyURL string `toml:"proxy_url"`
	// NoProxy is honored for custom proxies. Env/auto modes use NO_PROXY from the
	// process environment instead.
	NoProxy string             `toml:"no_proxy"`
	Proxy   NetworkProxyConfig `toml:"proxy"`
}

NetworkConfig controls ordinary outbound HTTP traffic such as model providers, updater checks, CodeGraph downloads, and web_fetch. web_fetch reuses these proxy settings while keeping its own SSRF-guarded dialer.

type NetworkProxyConfig

type NetworkProxyConfig struct {
	Type     string `toml:"type"` // http|https|socks5|socks5h
	Server   string `toml:"server"`
	Port     int    `toml:"port"`
	Username string `toml:"username"`
	Password string `toml:"password"`
}

NetworkProxyConfig is the structured custom-proxy editor shape. Password is optional and supports ${VAR} expansion, so users can avoid storing it literally.

type NotificationsConfig

type NotificationsConfig struct {
	Enabled         bool `toml:"enabled"`
	TurnDone        bool `toml:"turn_done"`
	ApprovalRequest bool `toml:"approval_request"`
	AskRequest      bool `toml:"ask_request"`
}

NotificationsConfig controls optional system notifications for CLI chat/run.

type PermissionsConfig

type PermissionsConfig struct {
	Mode  string   `toml:"mode"`
	Allow []string `toml:"allow"`
	Ask   []string `toml:"ask"`
	Deny  []string `toml:"deny"`
}

PermissionsConfig declares the per-call permission policy (see internal/permission). Mode is the fallback decision for writer tools when no rule matches ("ask" | "allow" | "deny"; default "ask"); read-only tools always fall back to allow. Allow/Ask/Deny are rule lists of the form "ToolName" or "ToolName(glob)". Precedence: deny > ask > allow > fallback.

type PluginEntry

type PluginEntry struct {
	Name    string            `toml:"name"`
	Type    string            `toml:"type"` // "stdio" (default) | "http" | "sse"
	Command string            `toml:"command"`
	Args    []string          `toml:"args"`
	Env     map[string]string `toml:"env"`
	URL     string            `toml:"url"`
	Headers map[string]string `toml:"headers"`
	// AutoStart controls whether the server connects during session startup.
	// Nil preserves historical behavior: configured servers start automatically.
	AutoStart *bool `toml:"auto_start"`
	// Tier selects how aggressively the server is connected at boot:
	//   "eager"      — blocks startup until the handshake completes; required for
	//                  servers whose tools the system prompt depends on.
	//   "lazy"       — registers placeholder tools immediately (from on-disk
	//                  schema cache when available) and only spawns the real
	//                  subprocess on first model use. Kept for legacy configs.
	//   "background" — placeholder + spawn fired at boot but not waited on;
	//                  swap happens once the spawn finishes.
	// Empty defaults to "background" so enabled MCPs connect automatically
	// without blocking chat. Unknown non-empty values fall back to "lazy".
	Tier string `toml:"tier"`
	// CallTimeout overrides the per-call default timeout (60s) for this MCP
	// server's JSON-RPC calls. Prevents a slow server from blocking the agent.
	// Format: Go duration string (e.g. "30s", "2m", "0" to disable).
	CallTimeout string `toml:"call_timeout"`
	// Risk marks this MCP server's tools' risk class for the permission gate
	// (SPEC v2 §3.2A). MCP tools default to "external" (safe: outward
	// operations need approval). Set to "read" for a trusted read-only server
	// so its tools don't prompt; "write_local"/"exec" for the in-between cases.
	// Empty/unknown = "external" (fail-safe). See internal/permission/risk.go.
	Risk string `toml:"risk"`
}

PluginEntry declares an external MCP server. Type selects the transport: "stdio" (default) launches Command/Args/Env as a subprocess; "http" (a.k.a. streamable-http) and "sse" connect to a remote URL with optional static Headers. String fields support ${VAR} / ${VAR:-default} expansion so secrets (bearer tokens, keys) come from the environment, not the file. The fields mirror Claude Code's mcpServers spec, so entries can come from either fairpeer.toml's [[plugins]] or a project-root .mcp.json (see loadMCPJSON).

func ClearPluginAuthenticationInSource

func ClearPluginAuthenticationInSource(name string) (PluginEntry, bool, string, error)

ClearPluginAuthenticationInSource clears auth material in the file that actually owns the MCP server. Load() merges user/project TOML and project .mcp.json into one Config, so callers must not mutate that merged view and Save() it back: a .mcp.json-only server would otherwise be serialized into fairpeer.toml or the user config. Source priority mirrors Load(): project TOML, user TOML, then the project .mcp.json entry if TOML did not define that server.

func LoadCCSwitchMCP

func LoadCCSwitchMCP() ([]PluginEntry, error)

LoadCCSwitchMCP reads MCP servers enabled for Codex from cc-switch and maps them to fairpeer plugin entries. Newer cc-switch stores servers in SQLite; older installs kept them in config.json(.migrated/.bak), so we support both.

func NormalizePluginCommandLine

func NormalizePluginCommandLine(e PluginEntry) (PluginEntry, bool)

NormalizePluginCommandLine repairs the common MCP copy/paste mistake where a tutorial's full command line is placed in command while args is left empty. Valid commands that are just paths with spaces are left untouched unless they are quoted or start with a known MCP runner such as npx/uvx/node.

func (PluginEntry) ExpandedPlugin

func (e PluginEntry) ExpandedPlugin() PluginEntry

ExpandedPlugin returns a copy of e with ${VAR} references expanded across the command, args, env values, url, and header values — the fields Claude Code also expands. The entry itself is left untouched.

func (PluginEntry) ResolvedTier

func (e PluginEntry) ResolvedTier() string

ResolvedTier returns the normalized tier ("eager"|"lazy"|"background") with the project default applied. Unknown values fall back to "lazy" so a typo never forces a slow boot.

func (PluginEntry) ShouldAutoStart

func (e PluginEntry) ShouldAutoStart() bool

type Profile

type Profile struct {
	Name              string   `toml:"name"`
	DisplayName       string   `toml:"display_name"`
	Model             string   `toml:"model"`               // overrides DefaultModel; "" = config default
	SubagentModel     string   `toml:"subagent_model"`      // overrides agent.subagent_model; "" = unchanged
	Effort            string   `toml:"effort"`              // overrides effort; "" = provider default
	SystemPromptAddon string   `toml:"system_prompt_addon"` // appended to resolved prompt; "" = unchanged
	SystemPromptFile  string   `toml:"system_prompt_file"`  // when set, replaces the resolved prompt entirely
	EnabledSkills     []string `toml:"enabled_skills"`      // whitelist; empty = all skills
	DisabledSkills    []string `toml:"disabled_skills"`     // extra-disabled on top of config
	Plugins           []string `toml:"plugins"`             // plugin name whitelist; empty = all plugins
	HiddenTools       []string `toml:"hidden_tools"`        // tools to Hide from main loop schemas; empty = all visible. Subagents still see them via FilterRegistry.
	WorkspaceType     string   `toml:"workspace_type"`      // "code" | "document"; frontend hint only
}

Profile is a named bundle of boot.Options overrides that switches the whole Controller between product modes — today "dev" (coding) and "cowork" (office). A profile does NOT replace configuration; it layers on top of it:

  • Model / SubagentModel / Effort override the resolved provider knobs, so a coWork profile can pin a cheaper/faster model without touching fairpeer.toml.
  • SystemPromptAddon is appended to the resolved system prompt (after the instruction/output-style/memory/skill folding in boot.Build), so a profile can bias behaviour (e.g. "you are an office agent") without owning the whole prompt. Empty means no change.
  • DisabledSkills / EnabledSkills flip skill availability. EnabledSkills is a whitelist: when non-empty, only those (plus anything the profile does not name) — see ResolveSkillDisabled for the exact merge. For Phase 0 both stay empty so the skill set is unchanged.
  • Plugins whitelists which [[plugins]] entries are visible. Empty = all plugins (unchanged behaviour), so dev stays dev until coWork opts in.
  • WorkspaceType is a frontend hint ("code" | "document") that selects the layout; the backend ignores it. It rides the profile so the switch is atomic across Go rebuild + React layout.

Design rationale: profile switching reuses the proven SetModelForTab rebuild flow (acquire shared host → snapshot history → Close → boot.Build → Resume). A profile is therefore just "a richer set of boot.Options inputs" — not a new runtime concept. Everything here is resolved once in config and consumed in boot.Build / desktop.app.

func DefaultProfiles

func DefaultProfiles() []Profile

DefaultProfiles returns the profiles effective when fairpeer.toml declares no [[profiles]]. The caller (Config.Profiles resolution) merges user entries on top of these by name.

func (*Profile) ResolveSkillDisabled

func (p *Profile) ResolveSkillDisabled(configDisabled []string) map[string]bool

ResolveSkillDisabled merges the config-wide disabled-skill set with a profile's skill overrides and returns the effective disabled set (skill name key → true).

Merge rules:

  • Start from cfg.DisabledSkillNames() (the [skills].disabled config).
  • Profile.DisabledSkills is additive (a profile can disable more).
  • Profile.EnabledSkills, when non-empty, is a whitelist: any skill NOT in it is disabled. This lets a future cowork profile expose only office skills. Empty EnabledSkills (Phase 0) means "no whitelist, keep all".

The returned map uses SkillNameKey normalization so it composes with the existing config-disabled set regardless of platform case rules.

type ProviderEntry

type ProviderEntry struct {
	Name      string   `toml:"name"`
	Kind      string   `toml:"kind"`
	BaseURL   string   `toml:"base_url"`
	Model     string   `toml:"model"`      // a single model (back-compat)
	Models    []string `toml:"models"`     // a vendor's model list (one base_url/key, many models)
	ModelsURL string   `toml:"models_url"` // auto-fetch models from this URL on startup
	Default   string   `toml:"default"`    // default model when Models is set (else Models[0])
	// FastModel is the lightweight model used for background/fast tasks
	// (dream/distill/rag-extract, scheduler time-parse). Empty = fall back to
	// Default at runtime. This is the per-provider "fast" role; the global
	// agent.fast_task_model can override it.
	FastModel     string            `toml:"fast_model"`
	APIKeyEnv     string            `toml:"api_key_env"`
	ContextWindow int               `toml:"context_window"`
	Price         *provider.Pricing `toml:"price"`
	// Thinking / Effort are provider-kind-specific knobs forwarded to the provider
	// via Config.Extra. The anthropic provider reads Thinking="adaptive" to enable
	// extended thinking and Effort ("low".."max") to tune depth. The
	// openai-compatible provider forwards Effort as reasoning_effort for
	// thinking-capable models.
	// Empty = provider default.
	Thinking string `toml:"thinking"`
	Effort   string `toml:"effort"`
	// ReasoningProtocol selects the request shape for OpenAI-compatible reasoning
	// models. Empty/auto uses the model capability registry plus endpoint
	// heuristics; none disables automatic reasoning controls for this provider.
	ReasoningProtocol string `toml:"reasoning_protocol"`
	// SupportedEfforts lists the /effort levels this provider/model exposes.
	// When non-empty, it overrides the built-in defaults. All providers
	// support the unified low/medium/high vocabulary by default. "auto" is
	// the implicit prefix — always accepted.
	SupportedEfforts []string `toml:"supported_efforts"`
	// DefaultEffort is the /effort level used when the user picks "auto" or
	// has not set Effort. Ignored when SupportedEfforts is empty.
	DefaultEffort string `toml:"default_effort"`
	// Vision enables image support for this provider. When true, user-attached
	// images are sent as image_url content parts. When false (default), images
	// are stripped before sending to avoid 400 errors from text-only models.
	Vision bool `toml:"vision"`
	// VisionDetail controls the image detail level sent to the API ("auto",
	// "low", "high"). Only effective when Vision is true.
	VisionDetail string `toml:"vision_detail"`
	// NoProxy reaches this provider's base_url directly, never through the proxy.
	// For China-only endpoints a foreign-exit proxy resets the TLS handshake (#2803).
	NoProxy bool `toml:"no_proxy"`
	// CodingOnly marks this provider as consuming a Coding Plan subscription
	// quota (vs the regular token quota). UI surfaces a "consumes subscription
	// quota" hint; optionally restricts to coding-tool use per vendor terms.
	CodingOnly bool `toml:"coding_only"`
	// Aggregator marks this provider as a model-aggregation platform that can
	// call multiple vendors' models under one endpoint+key (e.g. a Coding Plan).
	// UI groups these under an "aggregators" section; not a routing branch.
	Aggregator bool `toml:"aggregator"`
}

ProviderEntry declares a model provider instance. ContextWindow is the model's token budget; the harness compacts older history as a turn's prompt approaches it (see agent compaction). 0 disables compaction for the instance.

func (*ProviderEntry) APIKey

func (e *ProviderEntry) APIKey() string

APIKey resolves the entry's API key from its api_key_env.

func (*ProviderEntry) ChatModelList

func (e *ProviderEntry) ChatModelList() []string

ChatModelList returns ModelList filtered to likely chat/completion models. Non-chat models (TTS, STT, ASR, embedding, etc.) are excluded so they do not appear in the chat model picker. Use ModelList() only when the full raw provider model list is needed, such as config serialization, provider diagnostics, or model-fetch editing.

func (*ProviderEntry) Configured

func (e *ProviderEntry) Configured() bool

Configured reports whether the provider's api_key_env is set — the same check Validate enforces, so pickers can filter on it.

func (*ProviderEntry) DefaultModel

func (e *ProviderEntry) DefaultModel() string

DefaultModel returns the provider's default model: the explicit `default`, else the first of ModelList.

func (*ProviderEntry) FetchModels

func (e *ProviderEntry) FetchModels(ctx context.Context) ([]string, error)

FetchModels queries the provider's OpenAI-compatible GET /models endpoint and returns the available model IDs, sorted alphabetically.

func (*ProviderEntry) HasModel

func (e *ProviderEntry) HasModel(m string) bool

HasModel reports whether m is one of the provider's models.

func (*ProviderEntry) ModelList

func (e *ProviderEntry) ModelList() []string

ModelList returns the models this provider exposes: the explicit `models` list, or the single `model` as a one-element list (back-compat). Empty if neither set.

type QQBotConfig

type QQBotConfig struct {
	Enabled      bool   `toml:"enabled"`
	AppID        string `toml:"app_id"`
	AppSecretEnv string `toml:"app_secret_env"` // 环境变量名,如 QQ_BOT_APP_SECRET
}

QQBotConfig QQ 官方 Bot API v2 配置。

type RenderScope

type RenderScope string
const (
	RenderScopeFull    RenderScope = "full"
	RenderScopeUser    RenderScope = "user"
	RenderScopeProject RenderScope = "project"
)

type SMTPConfig

type SMTPConfig struct {
	Host           string `toml:"host"`            // SMTP server host, e.g. smtp.example.com
	Port           int    `toml:"port"`            // SMTP port (587 for STARTTLS, 465 for implicit TLS, 25 plain)
	From           string `toml:"from"`            // sender address, e.g. agent@example.com
	Username       string `toml:"username"`        // SMTP auth username (often = From); empty = no auth
	PasswordEnv    string `toml:"password_env"`    // env var holding the SMTP password (never stored)
	UseTLS         bool   `toml:"use_tls"`         // implicit TLS (port 465); false = STARTTLS/plain. DEPRECATED: use encryption_mode.
	EncryptionMode string `toml:"encryption_mode"` // "tls" (implicit, 465) | "starttls" (587) | "none" (25). Empty → migrate from use_tls.
}

SMTPConfig holds outbound mail server settings. Secrets come from the env via PasswordEnv (never stored in the TOML).

type SandboxConfig

type SandboxConfig struct {
	WorkspaceRoot string   `toml:"workspace_root"`
	AllowWrite    []string `toml:"allow_write"`
	// Bash is the OS-sandbox mode for the bash tool: "enforce" (default) jails
	// each command, "off" runs it unconfined. Phase 1; macOS only for now, with
	// a graceful fallback elsewhere (see internal/sandbox).
	Bash string `toml:"bash"`
	// Network allows network egress from inside the bash sandbox. Defaults true
	// so module/package downloads keep working; the boundary is then writes.
	Network bool `toml:"network"`
	// RequireAvailable, when true, makes bash mode "enforce" fail-closed (refuse
	// all commands) if no OS sandbox is available on this platform, rather than
	// silently degrading to unconfined. False (default) keeps the graceful
	// fallback so fairpeer stays usable on unsupported OSes.
	RequireAvailable bool `toml:"require_available"`
	// StrictWrites narrows the macOS Seatbelt toolchain-cache grants to true
	// cache subdirs only (~/.cargo/registry/cache, ~/Library/Caches, …) so a
	// prompt-injected command can't drop an executable in ~/.cargo/bin or
	// ~/.npm. Default false: the broad grants are needed for `go install`/
	// `cargo build`/`npm install` (they write to bin/pkg dirs). Turn on for
	// high-security deployments that don't run build tools. Audit A8. macOS only.
	StrictWrites bool `toml:"strict_writes"`
	// ReadRoots confines read_file/grep to these directories (opt-in). Empty =
	// unconfined reads (the default), because an agent legitimately reads /etc,
	// system headers, ~/.gitconfig, package caches, etc. A high-security
	// deployment that wants read/data isolation sets this; boot then wires
	// ConfineReaders to override the unconfined defaults. Note: bash is NOT
	// read-confined even when this is set, so this is defense-in-depth. Audit A7.
	ReadRoots []string `toml:"read_roots"`
}

SandboxConfig bounds the blast radius of tool calls (Phase 0: file-writer confinement). WorkspaceRoot is the directory the built-in file writers (write_file / edit_file / multi_edit) may modify; empty means the current working directory, so writes stay inside the project by default. AllowWrite lists extra directories writers may also touch (e.g. a sibling repo or a temp dir). Both support ${VAR} / ${VAR:-default} expansion. Reads are unrestricted; confining `bash` is Phase 1 (OS-level sandbox).

type SearchConfig

type SearchConfig struct {
	Engine string `toml:"engine"`
	RgPath string `toml:"rg_path"`
}

SearchConfig tunes the grep tool's engine. Engine is "auto" (default — use ripgrep when it's on PATH, else the native Go scanner), "native" (always Go), or "rg" (require ripgrep; warn at startup and fall back to native if absent). RgPath optionally points at a specific ripgrep binary instead of a PATH lookup.

type SkillsConfig

type SkillsConfig struct {
	Paths          []string `toml:"paths"`
	ExcludedPaths  []string `toml:"excluded_paths"`
	DisabledSkills []string `toml:"disabled_skills"`
	MaxDepth       int      `toml:"max_depth"`
}

SkillsConfig configures skill discovery. Paths adds extra "custom"-scope skill roots — each a directory of SKILL.md / <name>.md playbooks — scanned between the project roots (.fairpeer/.agents/.agent/.claude under the workspace) and the global roots. ExcludedPaths hides matching discovery roots without deleting folders. ~, relative paths, and ${VAR} expansion are supported. DisabledSkills hides named skills from the agent prompt, slash invocation, and skill tools while keeping them manageable.

type StatuslineConfig

type StatuslineConfig struct {
	Command string `toml:"command"`
}

StatuslineConfig configures a custom status line. Command, when set, is run at startup and after each turn; its first line of stdout replaces the built-in status data row. A JSON payload (model, context tokens, cwd) is fed on stdin.

type ToolsConfig

type ToolsConfig struct {
	Enabled            []string     `toml:"enabled"`
	BashTimeoutSeconds *int         `toml:"bash_timeout_seconds"`
	Search             SearchConfig `toml:"search"`
}

ToolsConfig selects which built-in tools are enabled. Empty means all of them.

type UIConfig

type UIConfig struct {
	Theme          string `toml:"theme"`           // auto|dark|light; empty resolves to auto
	ThemeStyle     string `toml:"theme_style"`     // graphite|aurora|slate|carbon|nocturne|amber and legacy aliases
	ShortcutLayout string `toml:"shortcut_layout"` // classic|desktop; accepted for compatibility
	CloseBehavior  string `toml:"close_behavior"`  // legacy desktop close behavior; prefer desktop.close_behavior
	ShowReasoning  bool   `toml:"show_reasoning"`  // Ctrl+O / /verbose: show thinking text in CLI; false = collapsed
}

UIConfig controls CLI presentation-only settings. Desktop appearance is kept in DesktopConfig so desktop preferences cannot alter terminal output or prompts.

type WeixinBotConfig

type WeixinBotConfig struct {
	Enabled   bool   `toml:"enabled"`
	AccountID string `toml:"account_id"`
	TokenEnv  string `toml:"token_env"` // 环境变量名,如 WEIXIN_BOT_TOKEN
	APIBase   string `toml:"api_base"`  // iLink API base URL
}

WeixinBotConfig 微信 iLink Bot 配置。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL