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
- Variables
- func ArchiveDir() string
- func BuildModelFetchURLs(baseURL, override string) ([]string, error)
- func CacheDir() string
- func CanonicalDesktopOfficialProviderName(name string) string
- func CanonicalSkillPath(path string) string
- func CommandDirs() []string
- func CommandDirsForRoot(root string) []string
- func CoworkPromptAddon(disabledSkills []string) string
- func EffectiveEffort(e *ProviderEntry) string
- func EffortDisplay(e *ProviderEntry) string
- func ExpandVars(s string) string
- func ImportCCSwitchMCP() (total, added, updated int, err error)
- func ImportCCSwitchMCPEntries(entries []PluginEntry) (total, added, updated int, err error)
- func IsLikelyChatModel(model string) bool
- func IsReservedPluginName(name string) (string, bool)
- func IsValidSkillName(name string) bool
- func MemoryUserDir() string
- func ModelRefsProvider(ref, name string) bool
- func NetdevPromptAddon(disabledSkills []string) string
- func NormalizeEffort(e *ProviderEntry, raw string) (string, error)
- func NormalizeLegacyDesktopProviderAccess(c *Config)
- func PluginAllowedByProfile(p *Profile, pluginName string) bool
- func PluginHiddenByProfile(p *Profile, pluginName string) bool
- func ProfileNameKey(name string) string
- func ProjectSessionDir(workspaceRoot string) string
- func ProjectSessionDirFor(workspaceRoot, profile string) string
- func ReasoningProtocolForEntry(e *ProviderEntry) string
- func ReasoningProtocolValid(raw string) bool
- func RenderTOML(c *Config) string
- func RenderTOMLForScope(c *Config, scope RenderScope) string
- func SaveMinimalProjectAutoPlan(path, mode string) (string, error)
- func SessionDir() string
- func SessionDirFor(profile string) string
- func SkillNameKey(name string) string
- func SourcePath() string
- func SourcePathForRoot(root string) string
- func UserConfigPath() string
- func UserCredentialsPath() string
- func ValidateNetDev(nd NetDevConfig) error
- func WorkspaceSlug(absPath string) string
- type AgentConfig
- type BotAllowlist
- type BotConfig
- type BotConnectionConfig
- type BotConnectionCredential
- type BotConnectionSessionMapping
- type BotDesktopWatcher
- type CodegraphConfig
- type Config
- func (c *Config) AddPermissionRule(list, rule string) error
- func (c *Config) AddSkillPath(path string) error
- func (c *Config) AmbientLocalPreset(name string) bool
- func (c *Config) AutoStartPlugins() []PluginEntry
- func (c *Config) BashMode() string
- func (c *Config) BashTimeoutSeconds() int
- func (c *Config) ClearPluginAuthentication(name string) (PluginEntry, bool, error)
- func (c *Config) DesktopCheckUpdates() bool
- func (c *Config) DesktopCloseBehavior() string
- func (c *Config) DesktopDisplayMode() string
- func (c *Config) DesktopLanguage() string
- func (c *Config) DesktopMetrics() bool
- func (c *Config) DesktopTelemetry() bool
- func (c *Config) DesktopTheme() string
- func (c *Config) DesktopThemeStyle() string
- func (c *Config) DisabledSkillNames() []string
- func (c *Config) ExcludeSkillPath(path string) error
- func (c *Config) IsProfileKnown(name string) bool
- func (c *Config) IsSkillDisabled(name string) bool
- func (c *Config) NetDevDeviceByName(name string) (NetDevDevice, bool)
- func (c *Config) NetDevGroupByName(name string) (NetDevGroup, bool)
- func (c *Config) NetDevHopByName(name string) (NetDevHop, bool)
- func (c *Config) NetworkProxyMode() string
- func (c *Config) NetworkProxySpec() netclient.ProxySpec
- func (c *Config) Provider(name string) (*ProviderEntry, bool)
- func (c *Config) ReadRoots() []string
- func (c *Config) RemovePermissionRule(list, rule string) (bool, error)
- func (c *Config) RemovePlugin(name string) bool
- func (c *Config) RemoveProvider(name string) error
- func (c *Config) RemoveSkillPath(path string) (bool, error)
- func (c *Config) ResolveModel(ref string) (*ProviderEntry, bool)
- func (c *Config) ResolveModelWithFallback(ref string) (resolvedRef string, fallback bool, ok bool)
- func (c *Config) ResolveProfile(name string) (*Profile, error)
- func (c *Config) ResolveSystemPrompt() (string, error)
- func (c *Config) RestoreSkillPath(path string) error
- func (c *Config) Save() error
- func (c *Config) SaveForRoot(root string) error
- func (c *Config) SaveTo(path string) error
- func (c *Config) SaveToScope(path string, scope RenderScope) error
- func (c *Config) SetAutoPlan(mode string) error
- func (c *Config) SetDefaultModel(name string) error
- func (c *Config) SetDesktopAppearance(theme, style string) error
- func (c *Config) SetDesktopCheckUpdates(enabled bool) error
- func (c *Config) SetDesktopCloseBehavior(mode string) error
- func (c *Config) SetDesktopDisplayMode(mode string) error
- func (c *Config) SetDesktopLanguage(lang string) error
- func (c *Config) SetDesktopMetrics(enabled bool) error
- func (c *Config) SetDesktopTelemetry(enabled bool) error
- func (c *Config) SetDreamEnabled(enabled bool)
- func (c *Config) SetDreamIntervals(dreamDays, distillDays int) error
- func (c *Config) SetExpandThinking(on bool) error
- func (c *Config) SetLanguage(lang string) error
- func (c *Config) SetNetwork(n NetworkConfig) error
- func (c *Config) SetPermissionMode(mode string) error
- func (c *Config) SetPlannerModel(name string) error
- func (c *Config) SetProviderEffort(name, effort string) error
- func (c *Config) SetProviderThinking(name, thinking string) error
- func (c *Config) SetShowReasoning(on bool) error
- func (c *Config) SetSkillEnabled(name string, enabled bool) error
- func (c *Config) SetUICloseBehavior(mode string) error
- func (c *Config) SetUIShortcutLayout(layout string) error
- func (c *Config) SkillCustomPaths() []string
- func (c *Config) SkillExcludedPaths() []string
- func (c *Config) SkillMaxDepth() int
- func (c *Config) UICloseBehavior() string
- func (c *Config) UIShortcutLayout() string
- func (c *Config) UITheme() string
- func (c *Config) UIThemeStyle() string
- func (c *Config) UpsertPlugin(e PluginEntry) error
- func (c *Config) UpsertProvider(e ProviderEntry) error
- func (c *Config) Validate(model string) error
- func (c *Config) WriteFile(path string) error
- func (c *Config) WriteRoots() []string
- func (c *Config) WriteRootsForRoot(fallbackRoot string) []string
- type CoworkConfig
- type DesktopConfig
- type DreamConfig
- type EffortCapability
- type EmailAccount
- type FeishuBotConfig
- type IMAPConfig
- type LLMConfig
- type LSPConfig
- type LSPServer
- type MCPImportCandidate
- type MigrationResult
- type MobileBridgeConfig
- type NetDevAlertRule
- type NetDevAssessment
- type NetDevConfig
- type NetDevDBSource
- type NetDevDevice
- type NetDevDiscovery
- type NetDevDockerConfig
- type NetDevFirewallConfig
- type NetDevGroup
- type NetDevGuardrails
- type NetDevHop
- type NetDevK8sConfig
- type NetDevLogFollow
- type NetDevPreset
- type NetDevProject
- type NetDevSNMP
- type NetDevSyslogConfig
- type NetDevTrapConfig
- type NetworkConfig
- type NetworkProxyConfig
- type NotificationsConfig
- type PermissionsConfig
- type PluginEntry
- type Profile
- type ProviderEntry
- func (e *ProviderEntry) APIKey() string
- func (e *ProviderEntry) ChatModelList() []string
- func (e *ProviderEntry) Configured() bool
- func (e *ProviderEntry) DefaultModel() string
- func (e *ProviderEntry) FetchModels(ctx context.Context) ([]string, error)
- func (e *ProviderEntry) HasModel(m string) bool
- func (e *ProviderEntry) ModelList() []string
- type QQBotConfig
- type RenderScope
- type SMTPConfig
- type SandboxConfig
- type SearchConfig
- type SkillsConfig
- type StatuslineConfig
- type TelegramBotConfig
- type ToolsConfig
- type TrustDomainConfig
- func (c TrustDomainConfig) AttestIntervalOrDefault() int
- func (c TrustDomainConfig) DataDirOrDefault() string
- func (c TrustDomainConfig) DiscoveryPortOrDefault() int
- func (c TrustDomainConfig) ListenAddrOrDefault() string
- func (c TrustDomainConfig) TickIntervalOrDefault() int
- func (c TrustDomainConfig) Validate() error
- type UIConfig
- type WeixinBotConfig
Constants ¶
const ( ReasoningProtocolAuto = "auto" ReasoningProtocolOpenAI = "openai" ReasoningProtocolMiniMax = "minimax" ReasoningProtocolNone = "none" )
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).
const ( NetDevPolicyReadOnly = "read-only" NetDevPolicyProposal = "proposal" NetDevPolicyProposalConf = "proposal+confirm2" )
NetDevGroupPolicy values.
const ( ToolScopeDefault = "" // full builtin tool surface ToolScopeNetDevOnly = "netdev-only" // no process-exec / file-write tools )
Tool scopes accepted in Profile.ToolScope.
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" )
const DefaultDistillInterval = 30
DefaultDistillInterval is the Distill run cadence when [dream].distill_interval is unset.
const DefaultDreamInterval = 7
DefaultDreamInterval is the Dream run cadence when [dream].dream_interval is unset.
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.
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.
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.
const DefaultSystemPrompt = `` /* 3605-byte string literal not displayed */
DefaultSystemPrompt is used when config provides none.
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 ¶
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).
var ValidReasoningProtocols = []string{ReasoningProtocolAuto, ReasoningProtocolOpenAI, ReasoningProtocolMiniMax, ReasoningProtocolNone}
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 ¶
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 ¶
CanonicalDesktopOfficialProviderName returns the Settings Center provider ID for built-in official provider aliases.
func CanonicalSkillPath ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- Multi-word substring check for compound terms that span separators (e.g. "text-embedding", "text-to-speech").
- 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
IsReservedPluginName reports whether name may not be used for a NEW MCP server, plus the human-readable reason (case-insensitive).
func IsValidSkillName ¶
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 ¶
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
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 ¶
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
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 ¶
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 ¶
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 ¶
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
ReasoningProtocolValid reports whether raw is one of the accepted values.
func RenderTOML ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
DesktopDisplayMode normalizes the transcript display mode. Default is "minimal" (collapsed model-generated intermediate items).
func (*Config) DesktopLanguage ¶
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 ¶
DesktopMetrics reports whether the desktop sends opt-in aggregate agent metrics — anonymous (signal, bucket) counters, never content. Default off.
func (*Config) DesktopTelemetry ¶
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 ¶
DesktopTheme normalizes desktop.theme. New desktop users default to the light graphite product look; an explicit auto/light/dark is preserved.
func (*Config) DesktopThemeStyle ¶
DesktopThemeStyle normalizes desktop.theme_style. Empty means the frontend chooses the default style for the resolved desktop theme.
func (*Config) DisabledSkillNames ¶
DisabledSkillNames returns valid disabled skill identifiers, preserving the first spelling and dropping duplicates/empty entries.
func (*Config) ExcludeSkillPath ¶
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 ¶
IsProfileKnown reports whether name resolves to a profile (builtin or configured). Empty returns true (resolves to dev).
func (*Config) IsSkillDisabled ¶
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
NetDevHopByName looks up a configured hop.
func (*Config) NetworkProxyMode ¶
NetworkProxyMode normalizes network.proxy_mode to a known value.
func (*Config) NetworkProxySpec ¶
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 ¶
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 ¶
RemovePermissionRule drops the first exact match of rule from the named list, reporting whether anything was removed.
func (*Config) RemovePlugin ¶
RemovePlugin deletes the named MCP server, reporting whether it was present.
func (*Config) RemoveProvider ¶
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 ¶
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 ¶
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 ¶
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 ¶
ResolveSystemPrompt returns the system prompt, reading system_prompt_file if set.
func (*Config) RestoreSkillPath ¶
RestoreSkillPath removes a pseudo-deleted skill source from excluded_paths.
func (*Config) Save ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SetDesktopAppearance sets desktop-only theme preferences. It must not affect CLI theme settings or provider-visible request data.
func (*Config) SetDesktopCheckUpdates ¶
SetDesktopCheckUpdates sets whether the desktop app checks for updates on startup. Manual checks remain available in Settings regardless of this value.
func (*Config) SetDesktopCloseBehavior ¶
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 ¶
SetDesktopDisplayMode sets the transcript display mode. UI-only.
func (*Config) SetDesktopLanguage ¶
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 ¶
SetDesktopMetrics sets whether the desktop sends opt-in aggregate agent metrics.
func (*Config) SetDesktopTelemetry ¶
SetDesktopTelemetry sets whether the desktop sends the anonymous launch ping.
func (*Config) SetDreamEnabled ¶
SetDreamEnabled toggles the background self-evolution master switch. When disabled, neither Dream nor Distill spawns automatically or via manual trigger.
func (*Config) SetDreamIntervals ¶
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 ¶
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 ¶
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 ¶
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 ¶
SetPlannerModel sets (or, with "", clears) agent.planner_model for two-model collaboration. A non-empty name must be a configured provider.
func (*Config) SetProviderEffort ¶
SetProviderEffort updates a provider's provider-specific thinking effort knob.
func (*Config) SetProviderThinking ¶
SetProviderThinking updates a provider's provider-specific thinking mode knob.
func (*Config) SetShowReasoning ¶
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 ¶
SetSkillEnabled persists a per-skill enable/disable preference. Skills are enabled by default; disabling records the name, enabling removes it.
func (*Config) SetUICloseBehavior ¶
SetUICloseBehavior is kept for callers compiled against the old edit API.
func (*Config) SetUIShortcutLayout ¶
historical behavior; "desktop" enables the two-axis desktop-style shortcuts.
func (*Config) SkillCustomPaths ¶
SkillCustomPaths returns the configured custom skill roots with ${VAR} expanded; empty entries are dropped.
func (*Config) SkillExcludedPaths ¶
SkillExcludedPaths returns configured skill roots that should be hidden from discovery, with ${VAR} expanded and empty entries dropped.
func (*Config) SkillMaxDepth ¶
SkillMaxDepth bounds nested skill discovery. Depth 3 favors bundled skill packs while Store keeps nested markdown safe by requiring descriptions.
func (*Config) UICloseBehavior ¶
UICloseBehavior is the legacy name for DesktopCloseBehavior.
func (*Config) UIShortcutLayout ¶
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 ¶
UITheme normalizes ui.theme (dark/light/auto). Empty or unrecognized falls back to "auto", which follows the OS shell preference.
func (*Config) UIThemeStyle ¶
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) WriteRoots ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
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
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 ¶
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.