config

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 29 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"
	ReasoningProtocolMiniMax = "minimax"
	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 (
	NetDevPolicyReadOnly     = "read-only"
	NetDevPolicyProposal     = "proposal"
	NetDevPolicyProposalConf = "proposal+confirm2"
)

NetDevGroupPolicy values.

View Source
const (
	ToolScopeDefault    = ""            // full builtin tool surface
	ToolScopeNetDevOnly = "netdev-only" // no process-exec / file-write tools
)

Tool scopes accepted in Profile.ToolScope.

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"
	// ProfileNetDev is the network-operations mode (NETDEV_SPEC). Its defining
	// property is the hard tool seal: no bash, no file-write tools — the
	// diagnostic hand is structurally read-only, and write operations only ever
	// happen through the human-approved proposal pipeline (P1+). Project-level
	// instructions are OFF: the session's subject is the network, not the
	// workspace, so a cloned repo must not steer device sessions.
	ProfileNetDev = "netdev"
)
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 = `` /* 3605-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).

ValidReasoningProtocols lists every accepted reasoning_protocol value — used for error messages so typos surface instead of silently degrading to auto (the old behaviour swallowed unknown values).

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. IsLikelyChatModel filters non-chat models (TTS/STT/embedding/rerank/image/ video…) out of the chat model picker. BLACKLIST-BY-TOKEN maintenance model: when a provider ships a NEW non-chat modality, its token must be added here manually (last additions: grok-imagine / sora → "imagine", "video"). Allowlist-style classification lives in the registry layer instead (ProviderTemplate.ReasoningModels); this function must stay zero-dependency.

func IsReservedPluginName added in v0.2.0

func IsReservedPluginName(name string) (string, bool)

IsReservedPluginName reports whether name may not be used for a NEW MCP server, plus the human-readable reason (case-insensitive).

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 NetdevPromptAddon added in v0.2.0

func NetdevPromptAddon(disabledSkills []string) string

NetdevPromptAddon returns the netdev system-prompt add-on with its skill routing rows pruned the same way CoworkPromptAddon does — the netdev addon carries a routing table for the inherited coding skill set, and a disabled skill must not keep its instruction row.

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 — EXCEPT when p.PluginAllowlist is set: then Plugins is a strict allowlist and an empty list hides every external MCP server (the netdev seal; an MCP with write/exec tools would otherwise punch through the structural read-only tool_scope, because MCP tool names are outside RemovePrefix's reach). When p is nil (no profile), all plugins are allowed.

func PluginHiddenByProfile added in v0.2.0

func PluginHiddenByProfile(p *Profile, pluginName string) bool

PluginHiddenByProfile reports whether pluginName is explicitly hidden by profile p's HiddenPlugins. Unlike the Plugins whitelist (which hides everything not named — user-installed servers included), HiddenPlugins hides only the NAMED servers, letting builtin profiles keep coding-domain MCPs (codegraph) out of office/netdev modes without touching servers the user installed for those modes. Comparison is case-insensitive; nil profile or empty list hides nothing.

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 ReasoningProtocolValid added in v0.2.0

func ReasoningProtocolValid(raw string) bool

ReasoningProtocolValid reports whether raw is one of the accepted values.

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 ValidateNetDev added in v0.2.0

func ValidateNetDev(nd NetDevConfig) error

ValidateNetDev checks the whole [netdev] section. Errors carry the entry name so a bad device in a long inventory is findable.

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"`
	TelegramUsers  []string `toml:"telegram_users"`
	QQGroups       []string `toml:"qq_groups"`
	FeishuGroups   []string `toml:"feishu_groups"`
	WeixinGroups   []string `toml:"weixin_groups"`
	TelegramGroups []string `toml:"telegram_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"`
	Telegram    TelegramBotConfig     `toml:"telegram"`
	Connections []BotConnectionConfig `toml:"connections"`
	// DesktopWatchers 是持久化的"桌面事件订阅"列表:哪些 IM 聊天要接收桌面
	// agent 的审批/提问/完成推送(/desktop watch on 订阅,跨重启保留)。
	DesktopWatchers []BotDesktopWatcher `toml:"desktop_watchers"`
}

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

type BotConnectionConfig

type BotConnectionConfig struct {
	ID              string                        `toml:"id"`
	Provider        string                        `toml:"provider"` // qq|feishu|weixin|telegram
	Domain          string                        `toml:"domain"`   // feishu|lark|weixin|qq|telegram
	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"`
	ChatType      string `toml:"chat_type"`
	ChatID        string `toml:"chat_id"`
	SessionID     string `toml:"session_id"`
	Scope         string `toml:"scope"`
	WorkspaceRoot string `toml:"workspace_root"`
	UpdatedAt     string `toml:"updated_at"`
}

type BotDesktopWatcher added in v0.2.0

type BotDesktopWatcher struct {
	Platform string `toml:"platform"`
	ChatType string `toml:"chat_type"`
	ChatID   string `toml:"chat_id"`
}

BotDesktopWatcher 是一条持久化的桌面事件订阅(哪个 IM 聊天收桌面推送)。

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 is opt-in (default false): users turn it on in Settings or by writing [codegraph] enabled = true; an explicit value always wins. 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"`
	// NetDev ([netdev]) is pinned to the USER config after the project merge
	// (pinNetDev in LoadForRoot): a cloned repo must never inject devices, hop
	// chains, or scan scopes. See internal/config/netdev.go and NETDEV_SPEC §7.3.
	NetDev     NetDevConfig     `toml:"netdev"`
	Skills     SkillsConfig     `toml:"skills"`
	Codegraph  CodegraphConfig  `toml:"codegraph"`
	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"`
	// MobileBridge configures the linkpeer mobile companion bridge (desktop ↔
	// phone P2P). Empty means mobilebridge uses its built-in defaults; the
	// LINKPEER_SIGNAL env var still overrides signal_url for ad-hoc dev.
	MobileBridge MobileBridgeConfig `toml:"mobilebridge"`
	// TrustDomain ([trustdomain]) is the private-network trust domain
	// ledger (docs/TRUSTDOMAIN_SPEC.md): cross-profile infrastructure,
	// off by default. Independent of agent profiles and providers.
	TrustDomain TrustDomainConfig `toml:"trustdomain"`
	// contains filtered or unexported fields
}

Config is fairpeer's runtime configuration.

func Default

func Default() *Config

Default returns the built-in default configuration with no providers. The keyless local presets (Ollama, llama.cpp) are injected by Load/LoadForEdit when no config file defines [[providers]]; cloud providers stay user-configured via the CLI setup wizard (fairpeer chat/run) or the desktop onboarding/settings panel. The local presets alone never suppress first-run onboarding (they are skipped by Configured()-gated fallbacks and onboarding checks).

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) AmbientLocalPreset added in v0.2.0

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

AmbientLocalPreset reports whether name is a keyless local preset that load auto-injected (no config file defines [[providers]]) and that the user has NOT explicitly added via desktop provider_access. Ambient presets exist so local models are one click away in Settings, but they must never become an implicit model choice: ResolveModelWithFallback skips them, and the desktop drops persisted tab models that resolve only to them. Clearing is opt-in per name — adding "ollama" leaves a sibling "llamacpp" ambient.

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) NetDevDeviceByName added in v0.2.0

func (c *Config) NetDevDeviceByName(name string) (NetDevDevice, bool)

NetDevDeviceByName looks up a configured device.

func (*Config) NetDevGroupByName added in v0.2.0

func (c *Config) NetDevGroupByName(name string) (NetDevGroup, bool)

NetDevGroupByName looks up a configured group.

func (*Config) NetDevHopByName added in v0.2.0

func (c *Config) NetDevHopByName(name string) (NetDevHop, bool)

NetDevHopByName looks up a configured hop.

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. Ambient local presets (injected, never added via provider_access) never resolve here: an explicit CLI --model still reaches them through ResolveModel, but tabs and fallbacks must not land on a provider the user never chose.

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. Reserved names (bot channels / builtins) are rejected for NEW entries.

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"`
	// BrowserAttachURL makes browser automation ATTACH to an already-running
	// debug-enabled browser instead of launching a fresh instance per task
	// (e.g. "http://127.0.0.1:9222" — the endpoint the desktop's managed
	// browser uses). Chromium only accepts CDP connections when started with
	// --remote-debugging-port (and, since Chrome 136, only with a non-default
	// user-data-dir), so the target is normally the dedicated managed browser
	// started from Settings → 办公, where logins persist and the window stays
	// open between tasks. Empty = current behavior (launch + own lifecycle
	// per browser_auto run).
	BrowserAttachURL string `toml:"browser_attach_url"`
	// 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"`
	// VoiceModel is the provider model ref for speech-to-text, used by voice
	// input (mic button) and audio-attachment understanding. E.g.
	// "stepfun/stepaudio-2.5-asr" or "zhipu/glm-asr-2512". It must point at an
	// OpenAI-compatible /audio/transcriptions endpoint. Independent of the main
	// chat model — any main model works, since audio is transcribed to text
	// first and the text is then sent to the main model. Empty = voice input
	// disabled (the mic button is disabled with a hint to configure a model).
	VoiceModel string `toml:"voice_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 MobileBridgeConfig added in v0.2.0

type MobileBridgeConfig struct {
	SignalURL   string   `toml:"signal_url"`   // linkpeer-signal K base URL; empty = DefaultConfig placeholder
	STUNServers []string `toml:"stun_servers"` // extra STUN servers for cross-network P2P (M3)
	LogLevel    string   `toml:"log_level"`    // trace|debug|info|warn|error; empty = info
	AutoConfirm bool     `toml:"auto_confirm"` // 联调:收到 exchange 自动确认,不等用户点允许
	// PairAddress 钉死配对二维码使用哪块网卡的 IP(多网卡环境用户手选)。
	// 空 = 自动:默认路由出口优先 + 全部真实网卡作为多候选(手机端自动匹配)。
	PairAddress string `toml:"pair_address"`
	// UDPKnock 单包敲门(M3 NAT 穿透辅助,默认关):S 从 ICE 同一 UDP
	// socket 向 C 的公网映射(srflx,经 KnockServer 探得)发敲门包,
	// 提前打开 S 侧 NAT。双对称 NAT 无效(协议 §7)。
	UDPKnock    bool   `toml:"udp_knock"`
	KnockServer string `toml:"knock_server"` // 敲门依赖的远程 STUN,如 stun:host:3478
	// CloudSignalURL 公网跳板 K(跨网配对/信令候选):非空时 S 维持第二条
	// 出站 WSS 长连到该云 K,二维码 relay 追加它为末位候选——手机同网自动
	// 选局域网直连,跨网回退到云 K 打洞。空 = 关(纯局域网/单 K,零云)。
	CloudSignalURL string `toml:"cloud_signal_url"`
	// TURN 中转兜底(跨网打洞全败时经 coturn 中继;ICE 仍优先直连)。
	// 凭据为 coturn use-auth-secret(REST)模式:user=时间戳,pass=HMAC。
	TURNEnabled bool     `toml:"turn_enabled"`
	TURNServers []string `toml:"turn_servers"` // 如 ["turn:signal.example.com:3478?transport=udp"]
	TURNUser    string   `toml:"turn_user"`
	TURNPass    string   `toml:"turn_pass"`
}

MobileBridgeConfig is the user-facing [mobilebridge] section. Only the fields a user realistically edits live here; the rest of mobilebridge.Config comes from DefaultConfig(). signal_url is what makes the bridge actually connect — set it to your linkpeer-signal K base URL, e.g. signal_url = "http://192.168.1.48:8080".

type NetDevAlertRule added in v0.2.0

type NetDevAlertRule struct {
	Name     string `toml:"name"`
	Metric   string `toml:"metric"`   // reachable | if_down_count | uptime_reset | flap_count | if_down_above_p90
	Op       string `toml:"op"`       // >= | <= | ==
	Value    int64  `toml:"value"`    // reachable: 1=up 0=down; uptime_reset: 1=reboot detected
	Severity string `toml:"severity"` // info | warning | critical
	Enabled  bool   `toml:"enabled"`
}

NetDevAlertRule is one threshold rule over the health snapshot.

type NetDevAssessment added in v0.2.0

type NetDevAssessment struct {
	EngagementID string   `toml:"engagement_id"`
	Scopes       []string `toml:"scopes"`
	Expires      string   `toml:"expires"`
	Approver     string   `toml:"approver"`
}

NetDevAssessment is the engagement envelope that must be present and valid to switch to assess mode (P5; declared now so configs are forward-stable).

type NetDevConfig added in v0.2.0

type NetDevConfig struct {
	Enabled bool             `toml:"enabled"`
	Trap    NetDevTrapConfig `toml:"trap"`
	// NotifyWebhook is the Finding notification outlet (NETDEV_SPEC_V2 §5.2):
	// generic JSON POST; empty = off. min severity: info|warning|critical.
	NotifyWebhook     string `toml:"notify_webhook"`
	NotifyMinSeverity string `toml:"notify_min_severity"`
	NotifyFormat      string `toml:"notify_format"` // generic | feishu | dingtalk | wecom
	// SMTP 通知出口(§5.2 追加):与 webhook 并行;密码入 secret store。
	NotifySMTPHost    string   `toml:"notify_smtp_host"`
	NotifySMTPPort    int      `toml:"notify_smtp_port"`
	NotifySMTPUser    string   `toml:"notify_smtp_user"`
	NotifySMTPPassEnv string   `toml:"notify_smtp_pass_env"`
	NotifySMTPFrom    string   `toml:"notify_smtp_from"`
	NotifySMTPTo      []string `toml:"notify_smtp_to"`
	// NotifyBotDest pushes through the embedded IM gateway (feishu:oc_xxx /
	// weixin:wxid_xxx / qq:group / telegram:chat) — empty = off.
	NotifyBotDest string `toml:"notify_bot_dest"`
	// BriefingPushTime schedules the daily briefing push ("08:00" local;
	// "" = off) through the notify outlets.
	BriefingPushTime string `toml:"briefing_push_time"`
	// NetworkName is the managed network's display name (e.g. "总部生产网") —
	// 运维页面的身份标识, like a coding workspace's project name.
	NetworkName          string          `toml:"network_name"`
	DefaultMode          string          `toml:"default_mode"` // diagnose | assess
	AuditRetention       string          `toml:"audit_retention"`
	ProxyDeviceTraffic   bool            `toml:"proxy_device_traffic"` // false: devices dialed directly, never via the shared HTTP proxy
	MaxSessionsPerDevice int             `toml:"max_sessions_per_device"`
	Devices              []NetDevDevice  `toml:"devices"`
	Hops                 []NetDevHop     `toml:"hops"`
	Groups               []NetDevGroup   `toml:"groups"`
	Discovery            NetDevDiscovery `toml:"discovery"`
	// ExtraRead extends the drivers' read-command tables at runtime
	// ([netdev.extra_read] with vendor tables — see NETDEV_SPEC B-1: the
	// knowledge-growth path; unknown commands stay refused until classified).
	ExtraRead map[string][]string `toml:"extra_read"`
	// WeakCredDict is a local password-dictionary file for the strong tier of
	// netdev_weak_cred / NetDevWeakCredCheck (completion-spec §5.2). Empty =
	// basic tier only. The file's CONTENT never enters config or exports.
	WeakCredDict string `toml:"weak_cred_dict"`
	// InspectionInterval schedules the read-battery sweep ("1h", "30m"; "" = off).
	InspectionInterval string `toml:"inspection_interval"`
	// ScheduledBaseline rides the scheduled inspection sweep: also run the
	// config-security baseline battery each scheduled pass (default off).
	ScheduledBaseline bool `toml:"scheduled_baseline"`
	// BackupInterval schedules the config-backup sweep ("1h", "24h"; "" = off):
	// every tick snapshots every managed device's running-config into the
	// versioned vault — the drift/history backbone.
	BackupInterval string           `toml:"backup_interval"`
	Assessment     NetDevAssessment `toml:"assessment"`
	// Guardrails are the per-ask / per-tool-call controls (NETDEV_SPEC §6):
	// they reach DOWN into every LLM turn, not just the mode level.
	Guardrails NetDevGuardrails `toml:"guardrails"`
	// Projects are site-level scopes (collections of device groups) for the
	// title-bar switcher — see NetDevProject.
	Projects []NetDevProject `toml:"projects"`
	// Presets are named diagnostic command batteries ("OSPF 邻居全套") the
	// device card can run in one click — each command still goes through the
	// sealed Exec path one by one.
	Presets []NetDevPreset `toml:"presets"`
	// LogFollow bounds the streaming tail -F follows (hard caps: lines, bytes,
	// duration — an unbounded follow streaming into the UI is an incident).
	LogFollow NetDevLogFollow `toml:"log_follow"`
	// DBSources are read-only database diagnostic endpoints (netdev_db_query):
	// the allowlist is exact-statement-prefix, the account itself must be
	// least-privilege (the real structural seal lives in the DB grants).
	DBSources []NetDevDBSource `toml:"db_sources"`
	// PollIntervalSeconds schedules the SNMP health sweep (0 = off): every
	// device carrying an [netdev.devices.*.snmp] block is polled for
	// reachability/uptime/interface status into the health snapshot.
	PollIntervalSeconds int `toml:"poll_interval_seconds"`
	// AlertRules turn health/syslog signals into auto-Findings (active →
	// resolved lifecycle). Evaluated on every health poll.
	AlertRules []NetDevAlertRule `toml:"alert_rules"`
	// Syslog is the passive receiver (UDP): devices point their syslog here;
	// lines aggregate per device and known-bad patterns auto-escalate to
	// Findings. Port 0 = off.
	Syslog NetDevSyslogConfig `toml:"syslog"`
}

NetDevConfig is the [netdev] section: the network-device operations inventory (devices, hops/jump hosts, groups) and its policies. Like Reasonix's [remote] it is a USER-GLOBAL security control: LoadForRoot pins it back to the user config after the project merge, so a cloned repo's fairpeer.toml can never inject devices, hop chains, or scan scopes that the agent would then connect to with the user's global credentials (NETDEV_SPEC §7.3). Secrets never live here: entries name credential env vars (*_env) whose values sit in the secret store under netdev/*.

type NetDevDBSource added in v0.2.0

type NetDevDBSource struct {
	Name        string   `toml:"name"`
	Type        string   `toml:"type"` // mysql | postgres | redis | mongodb | mssql | clickhouse | elasticsearch
	Host        string   `toml:"host"`
	Port        int      `toml:"port"`
	Username    string   `toml:"username"`
	PasswordEnv string   `toml:"password_env"`
	Database    string   `toml:"database"`
	Allowlist   []string `toml:"allowlist"`
	// Via tunnels the connection through the first named hop's SSH chain
	// (local forward; production DBs behind bastions, NETDEV_SPEC_V2 追加).
	Via []string `toml:"via"`
}

NetDevDBSource is one read-only database diagnostic endpoint. PasswordEnv names the secret-store entry; the allowlist is exact-statement prefixes ("SHOW PROCESSLIST" style) — no wildcards, no table-name patterns.

type NetDevDevice added in v0.2.0

type NetDevDevice struct {
	Name    string   `toml:"name"`
	Vendor  string   `toml:"vendor"` // huawei | cisco | zte
	OS      string   `toml:"os"`     // vrp8 | vrp5 | ios | iosxe | zxr10 …
	Model   string   `toml:"model"`
	Address string   `toml:"address"`
	Port    int      `toml:"port"` // 0 => 22
	Via     []string `toml:"via"`  // ordered hop names (route to the device)
	Group   string   `toml:"group"`
	// Role is the user's EXPLICIT device-class override for the topology icon
	// set (router/switch/firewall/ips/vpn/bastion/server/ap/cloud; Chinese
	// aliases accepted). Empty = infer (group words → model/name → vendor
	// default). This is the minimal non-GUI "manual override" of the parked
	// topology-overlay lot.
	Role          string   `toml:"role"`
	Protocols     []string `toml:"protocols"` // priority order: ssh, netconf(telnet 已裁决删除,§6.4)
	Username      string   `toml:"username"`
	PasswordEnv   string   `toml:"password_env"`
	IdentityFile  string   `toml:"identity_file"`
	PassphraseEnv string   `toml:"passphrase_env"`
	UseSSHConfig  bool     `toml:"use_ssh_config"`
	Encoding      string   `toml:"encoding"` // auto | utf-8 | gbk
	// OOBURL is the 带外启动器 deep link (NETDEV_SPEC_V2 §6.3): ESXi/堡垒/BMC
	// Web UI entry. FairPeer only launches the local browser/RDP client — no
	// RDP/VNC protocol in-product; the click is audited.
	OOBURL string      `toml:"oob_url"`
	SNMP   *NetDevSNMP `toml:"snmp"`
	// ConsolePort, when set, replaces the SSH dial with a serial console
	// line (COM3 on Windows — the USB-serial adapter plugged into the
	// switch's console port). No host keys, no auth: physical presence IS
	// the authorization; the read-only classifier still seals every command.
	// ConsoleBaud 0 => 9600, line format fixed at 8N1.
	ConsolePort string `toml:"console_port"`
	ConsoleBaud int    `toml:"console_baud"`
	// LogPaths whitelists additional log-directory roots for this device
	// (e.g. "/opt/app/logs", "/usr/local/tomcat/logs"). tail/head/grep/wc on
	// paths under /var/log or one of these roots classify as read — the
	// log-source path whitelist that feeds netdev_log_read and the classifier
	// bypass. Human-registered only, like every inventory field.
	LogPaths []string `toml:"log_paths"`
	// ConfigPaths whitelists server config-file roots for §7.3 配置文件管理
	// (e.g. "/etc/nginx"): snapshot/diff/drift reads and restore-verify
	// proposal steps are confined to these roots — same authorization model as
	// LogPaths, human-registered only. Edits NEVER happen in-product: change
	// products are submitted as file-upload proposal steps.
	ConfigPaths []string `toml:"config_paths"`
	// Kind is the DATA-PLANE discriminator (NETDEV_SPEC_V2 §2.1): "" derives
	// from vendor (backward compatible — existing devices unchanged);
	// "docker" / "k8s" enable their API clients below. vendor stays the CLI
	// driver story for network gear.
	Kind   string                `toml:"kind"` // "" | docker | k8s | firewall
	Docker *NetDevDockerConfig   `toml:"docker,omitempty"`
	K8s    *NetDevK8sConfig      `toml:"k8s,omitempty"`
	Fw     *NetDevFirewallConfig `toml:"firewall,omitempty"`
}

NetDevDevice is one managed network device (router/switch/firewall).

func (NetDevDevice) NDPortOrDefault added in v0.2.0

func (d NetDevDevice) NDPortOrDefault() int

NDPortOrDefault returns the configured port or 22.

type NetDevDiscovery added in v0.2.0

type NetDevDiscovery struct {
	Scopes        []string `toml:"scopes"`         // CIDR whitelist; probing outside is refused
	Rate          int      `toml:"rate"`           // parallel probe cap
	Mode          string   `toml:"mode"`           // tunnel | probe | auto
	ProbeFallback string   `toml:"probe_fallback"` // tunnel when netprobe can't deploy
	// NmapPath is the user-supplied nmap binary for the service-sweep
	// orchestrator (empty = LookPath("nmap"); absent = the feature refuses
	// with install guidance — the product orchestrates, never bundles).
	NmapPath string `toml:"nmap_path"`
	// NetprobePath is fairpeer's own netprobe binary (cmd/netprobe) for
	// in-network liveness sweeps — the user builds/copies it (often onto a
	// jump host, where SSH-tunnel probing can't reach); the product
	// orchestrates and parses, same grammar as nmap_path.
	NetprobePath string `toml:"netprobe_path"`
	// SnmpCommunity enables F2's sysDescr fingerprint on discovery: hosts
	// with an open 161 get ONE v2c GET (no retry). Empty (default) = off —
	// SNMP stays a per-device metrics channel only.
	SnmpCommunity string `toml:"snmp_community"`
	// HTTPProbe enables F3's application fingerprint (default off): one
	// standard GET / per open 80/443/8080/8443 — title/Server header and the
	// TLS certificate. Opt-in: it is the only discovery traffic that is more
	// than a TCP handshake + banner wait.
	HTTPProbe bool `toml:"http_probe"`
	// F4 pacing keys (spec §4.7). Zero values take the spec defaults; -1
	// disables where disabling is a legal posture.
	FastMode       bool `toml:"fast_mode"`          // rate x4 for authorized windows
	MaxHostsPerJob int  `toml:"max_hosts_per_job"`  // 0 => 65536 (one /16)
	WallSec        int  `toml:"discovery_wall_sec"` // 0 => 14400 (4h)
	PerHostDelayMS int  `toml:"per_host_delay_ms"`  // 0 => 800ms jitter; -1 = off
	CacheTTLHours  int  `toml:"cache_ttl_hours"`    // 0 => 24; -1 = always re-probe
	MaxHops        int  `toml:"max_hops"`           // 0 => 2 (clamped 1..4): recursion depth cap
	// NoMediumConfirm pre-checks /23-/21 nets on the plan card (the key is
	// inverted so Go's zero value keeps the SAFE default: medium nets stay
	// unchecked until the operator opts into trusting them).
	NoMediumConfirm bool `toml:"medium_no_confirm"`
}

NetDevDiscovery bounds network probing (the scope whitelist is one of the never-off guardrails, NETDEV_SPEC invariant 3).

type NetDevDockerConfig added in v0.2.0

type NetDevDockerConfig struct {
	// Socket: npipe:////./pipe/docker_engine (Windows local) |
	// unix:///var/run/docker.sock | tcp://host:2375 (inventory hosts only).
	Socket string `toml:"socket"`
}

NetDevDockerConfig is the kind=docker data plane: one Docker Engine endpoint, GET-only API paths (NETDEV_SPEC_V2 §2.2 — no client-side code path for POST/DELETE exists at all).

type NetDevFirewallConfig added in v0.2.0

type NetDevFirewallConfig struct {
	ApiTokenEnv string `toml:"api_token_env"` // secret-store key holding the REST API token
}

NetDevFirewallConfig is the kind=firewall data plane (NETDEV_SPEC_V2 §2.6): vendor REST monitor endpoints, GET-only. v1 vendor: fortinet (FortiOS /api/v2/monitor/* + read-only /api/v2/cmdb/* GETs).

type NetDevGroup added in v0.2.0

type NetDevGroup struct {
	Name         string `toml:"name"`
	Policy       string `toml:"policy"`        // read-only | proposal | proposal+confirm2
	ChangeWindow string `toml:"change_window"` // e.g. "tue,thu 22:00-24:00"; "" = any time
}

NetDevGroup carries the shared policy for a set of devices.

type NetDevGuardrails added in v0.2.0

type NetDevGuardrails struct {
	ConfirmEachCommand bool     `toml:"confirm_each_command"`
	TurnCommandBudget  int      `toml:"turn_command_budget"`
	AllowedGroups      []string `toml:"allowed_groups"`
}

NetDevGuardrails — fine-grained, per-interaction controls:

  • ConfirmEachCommand: every netdev_exec / netdev_netconf call pops an approval card BEFORE it runs (boot installs permission Ask rules; Ask outranks both readOnly-allow and YOLO mode, so even full-access mode keeps asking). The one knob that makes every tool call a control point.
  • TurnCommandBudget: max read commands per user turn (0 = unlimited). The frontend resets the counter on each submit; beyond the budget the tool refuses with a reminder instead of executing — runaway-loop protection at the turn level.
  • AllowedGroups: when non-empty, the agent may only see and touch devices in these groups (netdev_devices output is filtered too, so the model's world is scoped before the first token is spent).

type NetDevHop added in v0.2.0

type NetDevHop struct {
	Name          string `toml:"name"`
	Host          string `toml:"host"`
	Port          int    `toml:"port"`
	User          string `toml:"user"`
	IdentityFile  string `toml:"identity_file"`
	PassphraseEnv string `toml:"passphrase_env"`
	PasswordEnv   string `toml:"password_env"`
	ProxyJump     string `toml:"proxy_jump"` // comma-separated chain of other hop names
	UseSSHConfig  bool   `toml:"use_ssh_config"`
}

NetDevHop is a bastion/jump host on the route to devices. Hops are human-registered only — discovery results never auto-promote (NETDEV_SPEC invariant 5).

type NetDevK8sConfig added in v0.2.0

type NetDevK8sConfig struct {
	KubeconfigEnv string   `toml:"kubeconfig_env"` // secret-store key holding the kubeconfig YAML
	Context       string   `toml:"context"`        // pinned; "" = kubeconfig's current-context
	Namespaces    []string `toml:"namespaces"`     // allowed namespaces; empty = all
}

NetDevK8sConfig is the kind=k8s data plane: one kubeconfig (secret store) + a pinned context (NETDEV_SPEC_V2 §2.3 / appendix B-7: the tool layer accepts the TARGET NAME only — no kubeconfig content, context or server overrides, so no SSRF/context-escape surface).

type NetDevLogFollow added in v0.2.0

type NetDevLogFollow struct {
	MaxLines   int `toml:"max_lines"`   // 0 => 500
	MaxBytes   int `toml:"max_bytes"`   // 0 => 256 KiB
	MaxSeconds int `toml:"max_seconds"` // 0 => 600
}

NetDevLogFollow caps one streaming log follow.

func (NetDevLogFollow) Capped added in v0.2.0

func (f NetDevLogFollow) Capped() NetDevLogFollow

Capped returns the follow caps with defaults filled in.

type NetDevPreset added in v0.2.0

type NetDevPreset struct {
	Name     string   `toml:"name"`
	Commands []string `toml:"commands"`
	Vendors  []string `toml:"vendors"` // empty = all vendors
}

NetDevPreset is one saved diagnostic battery.

type NetDevProject added in v0.2.0

type NetDevProject struct {
	Name   string   `toml:"name"`
	Groups []string `toml:"groups"`
	Note   string   `toml:"note"`
}

NetDevProject is a SITE-level scope (the Mist "site" / industry site-first pattern): a named collection of device groups — one 机房 / 园区 / 客户网络. 运维标题栏带有项目切换器; rail, findings and proposals filter to the active project so the operator thinks "which site" first, exactly like every mainstream NMS console.

type NetDevSNMP added in v0.2.0

type NetDevSNMP struct {
	Version      string `toml:"version"` // v2c | v3
	CommunityEnv string `toml:"community_env"`
	Username     string `toml:"username"`
	AuthEnv      string `toml:"auth_env"`
	PrivEnv      string `toml:"priv_env"`
}

NetDevSNMP carries SNMP collector credentials for a device (env names only).

type NetDevSyslogConfig added in v0.2.0

type NetDevSyslogConfig struct {
	Port       int `toml:"port"`         // UDP listen port; 0 = off
	RatePerMin int `toml:"rate_per_min"` // per-device ingest cap (0 => 600)
}

NetDevSyslogConfig bounds the passive syslog receiver.

type NetDevTrapConfig added in v0.2.0

type NetDevTrapConfig struct {
	Port int `toml:"port"` // UDP listen port; 0 = off
}

NetDevTrapConfig bounds the passive SNMP trap receiver (v2c).

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 (unless PluginAllowlist)
	HiddenPlugins     []string `toml:"hidden_plugins"`      // NAMED plugins to hide (unlike Plugins, user-installed servers stay visible); empty = hide none
	PluginAllowlist   bool     `toml:"plugin_allowlist"`    // treat Plugins as a strict allowlist: empty list hides ALL external MCPs (netdev seal; builtinFloor pins it)
	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

	// ToolScope is the HARD tool seal (unlike HiddenTools, which only trims
	// main-loop schemas): "netdev-only" removes process-exec and file-write
	// tools from the Registry entirely, so subagents inherit the same removal
	// and a prompt-injected model cannot reach a write path through any tool.
	// Empty = the default full builtin surface. See NETDEV_SPEC §7.1.
	ToolScope string `toml:"tool_scope"`
	// SkillDomains lists the skill `domain:` frontmatter values this profile's
	// index shows. A USER skill declaring a domain outside the list is folded
	// out of the pinned index (visibility only — run_skill / /<name> still
	// execute it; the profile whitelist above governs shipped skills and is a
	// different, harder gate). Undomained user skills stay visible in every
	// profile. Empty = no folding (all user skills listed).
	SkillDomains []string `toml:"skill_domains"`
	// LoadProjectInstructions: nil = default (load the workspace's AGENTS.md
	// hierarchy and project memory as usual). Explicit false is for profiles
	// whose subject is NOT the workspace (netdev: the subject is the network)
	// — a cloned repo's instruction files must not steer device sessions.
	LoadProjectInstructions *bool `toml:"load_project_instructions"`
}

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.

func (*Profile) SealsExecutionTools added in v0.2.0

func (p *Profile) SealsExecutionTools() bool

SealsExecutionTools reports whether boot must strip process-exec and file-write tools from the Registry for this profile.

func (*Profile) SkipProjectInstructions added in v0.2.0

func (p *Profile) SkipProjectInstructions() bool

SkipProjectInstructions reports whether the workspace's project-level instruction docs must be excluded from this profile's session.

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 BuiltinLocalProviders added in v0.2.0

func BuiltinLocalProviders() []ProviderEntry

BuiltinLocalProviders returns fresh copies of the keyless local-model presets (Ollama, llama.cpp) that ship for every install: no API key, and they only ever talk to this machine. Load injects them when no config file defines [[providers]] — a file that does define providers replaces them wholesale, so existing setups never see surprise additions.

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 TelegramBotConfig added in v0.1.5

type TelegramBotConfig struct {
	Enabled  bool   `toml:"enabled"`
	TokenEnv string `toml:"token_env"` // 环境变量名,如 TELEGRAM_BOT_TOKEN
	APIBase  string `toml:"api_base"`  // 可选:自建 Bot API 服务器,空 = 官方 https://api.telegram.org
}

TelegramBotConfig Telegram Bot API 配置。鉴权用单个静态 Bot Token(@BotFather 颁发)。

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 TrustDomainConfig added in v0.2.0

type TrustDomainConfig struct {
	Enabled bool `toml:"enabled"`
	// SignalURL is the rendezvous point ("wss://host/knock"); empty means
	// LAN-only discovery (mDNS / bootstrap peers).
	SignalURL string `toml:"signal_url"`
	// DomainID is the genesis block hash (hex) — the domain's permanent
	// identity. Empty = this host is not yet admitted to any domain.
	DomainID string `toml:"domain_id"`
	// DataDir holds the ledger, identity key and related state. Empty =
	// <user config dir>/trustdomain.
	DataDir string `toml:"data_dir"`
	// BootstrapPeers lists known member addresses ("host:port"). Gossip is
	// pull-based, so a fleet configures these bidirectionally (mesh) —
	// spec §四/§17: servers should not rely on avahi/mDNS being present.
	BootstrapPeers []string `toml:"bootstrap_peers"`
	// ListenAddr is the TCP listener for inbound peers (default ":7123";
	// "127.0.0.1:0" for a private ephemeral port).
	ListenAddr string `toml:"listen_addr"`
	// Discover enables UDP-broadcast LAN discovery (spec §四①): signed
	// beacons on DiscoveryPort announce members of THIS domain; discovered
	// addresses merge into the peer set. Addresses only — trust stays with
	// the membership handshake.
	Discover bool `toml:"discover"`
	// DiscoveryPort is the UDP beacon port (default 7125).
	DiscoveryPort int `toml:"discovery_port"`
	// Admins lists founding/known admin public keys (base64, std encoding)
	// for display and admission flows; the authoritative set lives on the
	// chain itself.
	Admins []string `toml:"admins"`
	// QuorumM mirrors the domain's admin quorum threshold for pre-join
	// display; the chain is authoritative.
	QuorumM int `toml:"quorum_m"`

	// Limits tune the node loop (chain-internal caps like MaxBlockBytes
	// are constants in internal/trustdomain).
	AttestIntervalSec     int    `toml:"attest_interval_sec"`     // 0 = 60
	CheckpointEveryBlocks uint64 `toml:"checkpoint_every_blocks"` // 0 = off, manual only
	TickIntervalSec       int    `toml:"tick_interval_sec"`       // 0 = 5
}

TrustDomainConfig configures the private-network trust domain ledger (docs/TRUSTDOMAIN_SPEC.md §15.1). The domain is cross-profile infrastructure: it is off by default and independent of any agent profile.

Trust placement (spec §1.4 #3): SignalURL points at the UNTRUSTED rendezvous server (linkpeer-signal) used only to arrange peer-to-peer connections — it holds no authority and sees only metadata.

func (TrustDomainConfig) AttestIntervalOrDefault added in v0.2.0

func (c TrustDomainConfig) AttestIntervalOrDefault() int

AttestIntervalOrDefault returns the attestation cadence in seconds.

func (TrustDomainConfig) DataDirOrDefault added in v0.2.0

func (c TrustDomainConfig) DataDirOrDefault() string

DataDirOrDefault resolves the ledger home: explicit setting, else <fairpeer user state root>/trustdomain, else "" (OS dir unavailable — callers must require an explicit data_dir then).

func (TrustDomainConfig) DiscoveryPortOrDefault added in v0.2.0

func (c TrustDomainConfig) DiscoveryPortOrDefault() int

DiscoveryPortOrDefault returns the UDP beacon port.

func (TrustDomainConfig) ListenAddrOrDefault added in v0.2.0

func (c TrustDomainConfig) ListenAddrOrDefault() string

ListenAddrOrDefault returns the inbound peer listener address.

func (TrustDomainConfig) TickIntervalOrDefault added in v0.2.0

func (c TrustDomainConfig) TickIntervalOrDefault() int

TickIntervalOrDefault returns the gossip loop cadence in seconds.

func (TrustDomainConfig) Validate added in v0.2.0

func (c TrustDomainConfig) Validate() error

Validate applies cross-field sanity: a disabled section may hold anything (users pre-stage config), but an enabled one must be coherent.

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