config

package
v0.66.16 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultDeleteRetention = 24 * time.Hour

DefaultDeleteRetention is the soft-delete retention window used when [delete] retention is unset.

Variables

View Source
var SandboxSignalModes = []string{"isolated", "allow_same_sandbox", "allow_all"}

SandboxSignalModes are the accepted values for [sandbox] signal_mode. They mirror nono v0.66.0's security.signal_mode enum. Empty is also valid (inherit nono's base-profile default).

Functions

func DefaultAgentPrompt added in v0.35.0

func DefaultAgentPrompt() string

func DefaultTOML added in v0.19.0

func DefaultTOML() []byte

func Expand

func Expand(s string, vars TemplateVars) (string, error)

func ExpandPath added in v0.11.0

func ExpandPath(p string) string

func ExpandPathRelative added in v0.66.0

func ExpandPathRelative(p, baseDir string) string

ExpandPathRelative resolves a configured path deterministically: it expands a leading ~/, and resolves a still-relative path against baseDir (the directory holding the config file) rather than the process working directory, then cleans the result. This keeps a value like [approvals.builtin] config resolving to the same absolute path regardless of which directory the daemon or CLI happens to run from. An empty (or whitespace-only) path stays empty so callers can distinguish "unset" from a resolved path.

func ExpandSlice

func ExpandSlice(ss []string, vars TemplateVars) ([]string, error)

func IncludeEnvVarName added in v0.19.0

func IncludeEnvVarName(repoBasename string) string

func LegacyRuntimeDirs added in v0.11.0

func LegacyRuntimeDirs() []string

LegacyRuntimeDirs returns paths where older versions stored the socket and PID file (TMPDIR or /tmp fallbacks). Used during startup to detect and clean up an orphaned daemon after the socket location changed.

func ParseDurationWithDays added in v0.3.0

func ParseDurationWithDays(s string) (time.Duration, error)

func ResolveConfigPath added in v0.66.0

func ResolveConfigPath(explicit string) (path string, exists bool, err error)

ResolveConfigPath returns the config file that LoadOrDefault(explicit) would read and whether that file exists on disk. When explicit is set it is used verbatim. When empty, resolution mirrors LoadOrDefault: the profile/XDG path, falling back to the legacy macOS path only when the XDG file is absent and no profile is active. Diagnostics (e.g. gr doctor) use this so the reported and inspected file is the same one the CLI/daemon actually load.

func ResolvePath added in v0.16.5

func ResolvePath(p string) string

func ResolveProfile added in v0.18.0

func ResolveProfile() (profile string, appName string, err error)

Types

type Agent

type Agent struct {
	Command           string                     `json:"command"                       toml:"command"`
	Args              []string                   `json:"args,omitempty"                toml:"args"`
	ResumeArgs        []string                   `json:"resume_args,omitempty"         toml:"resume_args"`
	ForkArgs          []string                   `json:"fork_args,omitempty"           toml:"fork_args"`
	Env               map[string]string          `json:"env,omitempty"                 toml:"env"`
	IdleTimeout       string                     `json:"idle_timeout,omitempty"        toml:"idle_timeout"`
	InjectPrompt      *bool                      `json:"inject_prompt,omitempty"       toml:"inject_prompt"`
	PreTrustWorkspace *bool                      `json:"pre_trust_workspace,omitempty" toml:"pre_trust_workspace"`
	Sandbox           SandboxConfig              `json:"sandbox"                       toml:"sandbox"`
	MCPServers        map[string]MCPServerConfig `json:"mcp_servers,omitempty"         toml:"mcp_servers"`
	ValidateModel     string                     `json:"validate_model,omitempty"      toml:"validate_model"`
	// InterruptCount is how many times the interrupt byte (Ctrl-C, 0x03) is sent
	// to interrupt this agent, and InterruptDelayMs is the pause in milliseconds
	// between successive sends. Some agent TUIs ignore a single Ctrl-C and need
	// two rapid presses to actually interrupt (Claude's TUI wants ~200ms apart),
	// so both are configurable per agent. Unset means the built-in defaults
	// (count 1, delay 0). See issue #620.
	InterruptCount   *int `json:"interrupt_count,omitempty"    toml:"interrupt_count"`
	InterruptDelayMs *int `json:"interrupt_delay_ms,omitempty" toml:"interrupt_delay_ms"`
}

func (Agent) IdleTimeoutDuration

func (a Agent) IdleTimeoutDuration() time.Duration

func (Agent) InterruptCountValue added in v0.66.2

func (a Agent) InterruptCountValue() int

InterruptCountValue returns how many times the interrupt byte (Ctrl-C, 0x03) should be sent to interrupt this agent. Defaults to 1 when unset; a value below 1 is clamped to 1 so an interrupt always sends at least once.

func (Agent) InterruptDelay added in v0.66.2

func (a Agent) InterruptDelay() time.Duration

InterruptDelay returns the pause between successive interrupt bytes. Defaults to 0 (send back-to-back) when unset; a negative value is treated as 0.

func (Agent) PreTrustWorkspaceEnabled added in v0.48.0

func (a Agent) PreTrustWorkspaceEnabled() bool

func (Agent) PromptInjectionEnabled added in v0.32.0

func (a Agent) PromptInjectionEnabled() bool

type Approvals added in v0.13.0

type Approvals struct {
	// Enabled controls whether the PreToolUse approve-request gating hook is
	// installed. nil (unset) means disabled: the status/lifecycle hooks are
	// still installed but the approval gate is not, because unattended agents
	// otherwise see their own tool calls as human-rejected and the OS sandbox
	// is the intended guardrail. Set to true to opt back into human approval
	// gating.
	Enabled *bool `toml:"enabled"`
	// Backend selects who makes the automated decision: "" (none — always
	// prompt the human), "command"/"external" (delegate to a command over
	// graith's JSON contract), "localmost" (the real localmost binary over its
	// native protocol), or "builtin" (graith's built-in localmost-compatible
	// engine). It is the canonical selector; Mode is the deprecated predecessor.
	Backend string           `toml:"backend"`
	Mode    string           `toml:"mode"`
	AutoPop bool             `toml:"auto_pop"`
	Timeout string           `toml:"timeout"`
	Command string           `toml:"command"`
	Builtin ApprovalsBuiltin `toml:"builtin"`
}

func (Approvals) HookEnabled added in v0.65.0

func (a Approvals) HookEnabled() bool

HookEnabled reports whether the approve-request PreToolUse hook should be installed. Defaults to false when unset — approval gating is opt-in.

func (Approvals) ResolveBackend added in v0.64.4

func (a Approvals) ResolveBackend() (backend, deprecation string, err error)

ResolveBackend resolves the effective approvals backend, applying back-compat for the deprecated Mode field. It returns the backend name, a non-empty deprecation message when a legacy Mode value was used (callers log it once), and an error for an unknown backend or a conflicting Mode+Backend pair.

Resolution order:

  1. If Backend is set, use it. If a legacy Mode is ALSO set and maps to a different backend, that is a hard error (refuse to guess intent).
  2. Else if Mode is one of command/external/localmost, map it to the "command" backend (historical behaviour) and return a deprecation message. A Mode with no Backend is always a warning, never an error.
  3. Else, the "prompt" backend (no automation).

func (Approvals) TimeoutDuration added in v0.13.0

func (a Approvals) TimeoutDuration() time.Duration

func (Approvals) Validate added in v0.64.5

func (a Approvals) Validate() error

Validate checks the [approvals] config for static contradictions that would otherwise only surface as an opaque fail-closed session crash at create time (see #740). It rejects an unknown or conflicting backend/mode (via ResolveBackend) and a command key set for a resolved backend that ignores it. Backend *availability* (command present, localmost binary on PATH, builtin config loadable) is still deferred to session-create by the daemon.

type ApprovalsBuiltin added in v0.64.4

type ApprovalsBuiltin struct {
	// Config is the path to a localmost-format config.json (allow/deny rules).
	Config string `toml:"config"`

	// Allow and Deny are the inline allow/deny rulesets. Each element is either
	// a bare rule string ("@arg @*") or a table with per-rule keys
	// (rule/unless/redirect/pipe). They are decoded as []any so both TOML forms
	// — an array of strings and an array of tables ([[approvals.builtin.allow]])
	// — are accepted, then converted to the localmost schema (see InlineJSON).
	Allow []any `toml:"allow"`
	Deny  []any `toml:"deny"`

	// AllowSafeXargs and AskNoninteractive mirror the localmost top-level flags.
	// nil means unset (the engine's default of true applies).
	AllowSafeXargs    *bool `toml:"allowSafeXargs"`
	AskNoninteractive *bool `toml:"askNoninteractive"`
}

ApprovalsBuiltin configures the built-in localmost-compatible engine. Rules can be supplied either as a path to an external localmost-format config.json (Config), or inline in config.toml via Allow/Deny/AllowSafeXargs/ AskNoninteractive. The two forms are mutually exclusive (see Approvals.Validate).

func (ApprovalsBuiltin) HasInline added in v0.66.0

func (b ApprovalsBuiltin) HasInline() bool

HasInline reports whether any inline ruleset field is set. When true, the rules are read from config.toml rather than an external Config file. An empty array (allow = []) defines no rules and does not count as inline, so it does not spuriously conflict with an external Config path.

func (ApprovalsBuiltin) InlineJSON added in v0.66.0

func (b ApprovalsBuiltin) InlineJSON() ([]byte, error)

InlineJSON renders the inline ruleset as localmost-format config.json bytes, so the existing (tested) localmost parser can compile it. The TOML keys map 1:1 to the localmost JSON schema (allow/deny/allowSafeXargs/askNoninteractive, and per-rule rule/unless/redirect/pipe), so a plain JSON re-encode suffices.

type Config

type Config struct {
	DefaultAgent     string             `toml:"default_agent"`
	GitHubUsername   string             `toml:"github_username"`
	BranchPrefix     string             `toml:"branch_prefix"`
	DataDir          string             `toml:"data_dir"`
	FetchOnCreate    bool               `toml:"fetch_on_create"`
	AgentPrompt      string             `toml:"agent_prompt"`
	AllowedRepoPaths []string           `toml:"allowed_repo_paths"`
	Repos            []RepoConfig       `toml:"repos"`
	StatusBar        StatusBar          `toml:"status_bar"`
	Keybindings      Keybindings        `toml:"keybindings"`
	Notifications    Notifications      `toml:"notifications"`
	Messages         Messages           `toml:"messages"`
	Delete           Delete             `toml:"delete"`
	Sandbox          SandboxConfig      `toml:"sandbox"`
	Approvals        Approvals          `toml:"approvals"`
	Status           StatusConfig       `toml:"status"`
	GitPull          GitPullConfig      `toml:"git_pull"`
	PRWatch          PRWatchConfig      `toml:"pr_watch"`
	MCPServers       []MCPServerConfig  `toml:"mcp_servers"`
	Overlay          Overlay            `toml:"overlay"`
	Orchestrator     OrchestratorConfig `toml:"orchestrator"`
	Remote           RemoteConfig       `toml:"remote"`
	Input            InputConfig        `toml:"input"`
	Agents           map[string]Agent   `toml:"agents"`
}

func Default

func Default() *Config

func Load

func Load(path string) (*Config, error)

func LoadOrDefault

func LoadOrDefault(path string) (*Config, error)

func (*Config) AvailableRepoPaths added in v0.66.13

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

AvailableRepoPaths returns the repo paths the orchestrator may use, combining the allowed_repo_paths list and the [[repos]] entries with ~ expanded, in config order and de-duplicated. It returns nil when none are configured.

func (*Config) FindRepo added in v0.18.0

func (c *Config) FindRepo(repoPath string) (RepoConfig, bool)

func (*Config) OrchestratorSandboxMerged added in v0.46.0

func (c *Config) OrchestratorSandboxMerged(agentName string) SandboxConfig

func (*Config) RepoPathAllowed added in v0.11.0

func (c *Config) RepoPathAllowed(repoPath string) bool

func (*Config) Validate added in v0.19.0

func (c *Config) Validate() error

type Delete added in v0.66.16

type Delete struct {
	Retention string `toml:"retention"`
}

Delete configures the soft-delete behaviour of `gr delete`. When retention is a positive duration, `gr delete` marks a session deleted and keeps its worktree/state for the window; the daemon purges it after the window elapses. A retention of "0" disables soft delete: `gr delete` is then rejected (with a message pointing at `gr purge`), since delete must never destroy — `gr purge` remains the way to hard-delete immediately.

func (Delete) RetentionDuration added in v0.66.16

func (d Delete) RetentionDuration() time.Duration

RetentionDuration resolves the configured soft-delete retention window. An unset value defaults to DefaultDeleteRetention (24h); "0" (or any zero duration) disables soft delete. An unparseable value falls back to the default so a typo never silently turns off recovery.

type GitPullConfig added in v0.42.0

type GitPullConfig struct {
	Enabled  bool   `toml:"enabled"`
	Interval string `toml:"interval"`
}

func (GitPullConfig) IntervalDuration added in v0.42.0

func (g GitPullConfig) IntervalDuration() time.Duration

type InputConfig added in v0.66.16

type InputConfig struct {
	// DragArrowKeys enables touch/hold-and-drag arrow keys:
	// press-and-hold the left mouse button then drag to emit discrete arrow-key
	// presses to the focused pane. Off by default because it repurposes
	// left-drag (which terminals otherwise use for text selection). Mouse-wheel
	// scrolling is always passed through unchanged.
	DragArrowKeys bool `toml:"drag_arrow_keys"`
	// DragArrowThreshold is the number of cells of drag movement that produces
	// one arrow-key press. Values below 1 fall back to the default.
	DragArrowThreshold int `toml:"drag_arrow_threshold"`
}

InputConfig is the optional [input] block controlling terminal input gestures in the attach passthrough loop.

type Keybindings

type Keybindings struct {
	Prefix              string `toml:"prefix"`
	NewSession          string `toml:"new_session"`
	ForkSession         string `toml:"fork_session"`
	DeleteSession       string `toml:"delete_session"`
	Detach              string `toml:"detach"`
	SessionList         string `toml:"session_list"`
	NextSession         string `toml:"next_session"`
	PrevSession         string `toml:"prev_session"`
	LastSession         string `toml:"last_session"`
	ResumeSession       string `toml:"resume_session"`
	RenameSession       string `toml:"rename_session"`
	Search              string `toml:"search"`
	ScrollMode          string `toml:"scroll_mode"`
	Shell               string `toml:"shell"`
	OrchestratorSession string `toml:"orchestrator_session"`
}

type MCPServerConfig added in v0.22.0

type MCPServerConfig struct {
	Name          string            `json:"-"              toml:"name"`
	Command       string            `json:"command"        toml:"command"`
	Args          []string          `json:"args,omitempty" toml:"args,omitempty"`
	Env           map[string]string `json:"env,omitempty"  toml:"env,omitempty"`
	Disabled      bool              `json:"-"              toml:"disabled,omitempty"`
	Sandbox       *bool             `json:"-"              toml:"sandbox,omitempty"`
	SandboxConfig *SandboxConfig    `json:"-"              toml:"sandbox_config,omitempty"`
}

func MergeMCPServers added in v0.22.0

func MergeMCPServers(global []MCPServerConfig, overrides map[string]MCPServerConfig) []MCPServerConfig

type Messages added in v0.3.0

type Messages struct {
	MaxAge       string `toml:"max_age"`
	MaxPerStream int    `toml:"max_per_stream"`
}

func (Messages) MaxAgeDuration added in v0.3.0

func (m Messages) MaxAgeDuration() time.Duration

type Notifications added in v0.2.0

type Notifications struct {
	Enabled    bool   `toml:"enabled"`
	OnApproval bool   `toml:"on_approval"`
	OnStopped  bool   `toml:"on_stopped"`
	Command    string `toml:"command"`
}

type OrchestratorConfig added in v0.42.0

type OrchestratorConfig struct {
	Enabled     bool                      `toml:"enabled"`
	Agent       string                    `toml:"agent"`
	Model       string                    `toml:"model"`
	IdleTimeout string                    `toml:"idle_timeout"`
	Prompt      string                    `toml:"prompt"`
	PromptFile  string                    `toml:"prompt_file"`
	Sandbox     OrchestratorSandboxConfig `toml:"sandbox"`
}

func (OrchestratorConfig) AgentName added in v0.42.0

func (o OrchestratorConfig) AgentName() string

func (OrchestratorConfig) IdleTimeoutDuration added in v0.42.0

func (o OrchestratorConfig) IdleTimeoutDuration() time.Duration

type OrchestratorSandboxConfig added in v0.46.0

type OrchestratorSandboxConfig struct {
	ReadDirs   []string `toml:"read_dirs"`
	WriteDirs  []string `toml:"write_dirs"`
	ReadFiles  []string `toml:"read_files"`
	WriteFiles []string `toml:"write_files"`
}

type Overlay added in v0.56.0

type Overlay struct {
	ShortcutKeys string `toml:"shortcut_keys"`
}

type PRWatchConfig added in v0.59.0

type PRWatchConfig struct {
	Enabled               bool   `toml:"enabled"`
	NotifyCIFailures      bool   `toml:"notify_ci_failures"`
	NotifyMergeConflicts  bool   `toml:"notify_merge_conflicts"`
	NotifyReviewComments  bool   `toml:"notify_review_comments"`
	NotifyPRComments      bool   `toml:"notify_pr_comments"`
	NotifyReviewDecisions bool   `toml:"notify_review_decisions"`
	NotifyPRLifecycle     bool   `toml:"notify_pr_lifecycle"`
	NotifyCIRecovery      bool   `toml:"notify_ci_recovery"`
	PollPending           string `toml:"poll_pending"`
	PollTerminal          string `toml:"poll_terminal"`
	PollMerged            string `toml:"poll_merged"`
	MaxNotificationsPerPR int    `toml:"max_notifications_per_pr"`
	Debounce              string `toml:"debounce"`
}

PRWatchConfig controls the PR & CI awareness loop, which resolves each session's GitHub PR via the gh CLI, polls its CI checks and review comments, and notifies the owning session's inbox on meaningful transitions.

Every notify_* sub-option defaults on: enabling pr_watch is meant to be a single switch (enabled = true) that turns on all notifications, and users selectively disable the classes they don't want. The classes are still gated separately because they carry different authority — a CI failure is a machine verdict (safe to act on), while a review comment or decision is human intent that may not be actionable — so each can be turned off independently.

Comments come in two distinct kinds, each with its own gate:

  • NotifyReviewComments covers inline code-review comments (the pulls/{n}/comments surface) — feedback anchored to a file and line.
  • NotifyPRComments covers regular conversation comments on the PR thread (the issues/{n}/comments surface) — issue-style comments not tied to a line of code.

They are separate signals: a reviewer leaving inline nits and someone dropping a "ship it" on the conversation thread differ, and a user may want one without the other.

For backward compatibility, notify_pr_comments used to be folded into notify_review_comments; see applyPRWatchCommentCompat, which keeps an older config that only set notify_review_comments delivering conversation comments.

func (PRWatchConfig) DebounceDuration added in v0.59.0

func (p PRWatchConfig) DebounceDuration() time.Duration

DebounceDuration is the minimum cooldown between notifications to one session.

func (PRWatchConfig) MaxNotifications added in v0.59.0

func (p PRWatchConfig) MaxNotifications() int

MaxNotifications returns the per-head-SHA notification cap, defaulting to 10.

func (PRWatchConfig) PollMergedDuration added in v0.59.0

func (p PRWatchConfig) PollMergedDuration() time.Duration

PollMergedDuration is the sweep interval for merged/closed PRs.

func (PRWatchConfig) PollPendingDuration added in v0.59.0

func (p PRWatchConfig) PollPendingDuration() time.Duration

PollPendingDuration is the poll interval while a PR has pending/in-progress checks.

func (PRWatchConfig) PollTerminalDuration added in v0.59.0

func (p PRWatchConfig) PollTerminalDuration() time.Duration

PollTerminalDuration is the poll interval once all checks are terminal (PR still open).

type PairRate added in v0.66.3

type PairRate struct {
	Count int
	Per   time.Duration
}

PairRate is a parsed pair_request_rate: Count events per Per duration.

func ParsePairRequestRate added in v0.66.3

func ParsePairRequestRate(s string) (PairRate, error)

ParsePairRequestRate parses a "<n>/<unit>" rate such as "5/min". The unit is one of sec/min/hour (with the aliases second/minute/hour). The count must be a positive integer. Any other shape is a hard error (fail-closed).

type Paths

type Paths struct {
	Profile    string
	AppName    string
	ConfigFile string
	DataDir    string
	RuntimeDir string
	SocketPath string
	PIDFile    string
	StateFile  string
	LogDir     string
	DaemonLog  string
	MessagesDB string
	TmpDir     string
}

func ResolvePaths

func ResolvePaths() (Paths, error)

func (Paths) EnsureDirs

func (p Paths) EnsureDirs() error

func (Paths) WithDataDir added in v0.21.0

func (p Paths) WithDataDir(dataDir string) Paths

type RemoteConfig added in v0.66.3

type RemoteConfig struct {
	// Enabled turns the remote listener on. Off by default; when false the rest
	// of the block is not validated so a disabled block never blocks startup.
	Enabled bool `toml:"enabled"`
	// Mode selects the transport: "tsnet" (embedded Tailscale via tsnet) or
	// "interface" (bind the host's existing tailnet interface IP).
	Mode string `toml:"mode"`
	// Hostname is the tsnet node name / MagicDNS label (tsnet mode).
	Hostname string `toml:"hostname"`
	// Port is the TCP port the listener binds.
	Port int `toml:"port"`
	// AuthKeyFile is the path to a tsnet auth key (tsnet mode only).
	AuthKeyFile string `toml:"auth_key_file"`
	// Tags are the tsnet ACL tags applied to the node (tsnet mode only).
	Tags []string `toml:"tags"`
	// AllowTailnetUsers is the WhoIs allowlist (Gate 1). Entries are either a
	// tailnet user email or a "tag:"-prefixed tag. A bare "tag:" entry opts
	// tagged nodes in; with no tag entry, tagged nodes are disallowed.
	AllowTailnetUsers []string `toml:"allow_tailnet_users"`
	// RequirePairing requires per-device pairing (Gate 2) for human-level
	// rights. Defaults to true; false is UNSAFE (trusts the tailnet identity
	// alone) and is restricted to read-only access — see the design doc §B.2.
	RequirePairing bool `toml:"require_pairing"`
	// PairRequestRate is the anti-flood limit on pending pair requests, written
	// "<n>/<unit>" (e.g. "5/min"); units are sec, min, or hour. Empty means no
	// configured limit here (the daemon applies its own default).
	PairRequestRate string `toml:"pair_request_rate"`
}

RemoteConfig is the optional, off-by-default [remote] block that exposes a tailnet-facing control listener (see the native-app design doc §A.4/§B). It is fail-closed: when Enabled, an invalid block is a hard config-load error (static validation only — runtime listener provisioning failures, e.g. a missing tailnet IP or cert, are handled by the remote listener, not here).

func (RemoteConfig) AllowsTaggedNodes added in v0.66.3

func (r RemoteConfig) AllowsTaggedNodes() bool

AllowsTaggedNodes reports whether any allow_tailnet_users entry opts tagged nodes in (a "tag:"-prefixed entry). With no such entry, tagged nodes — which WhoIs resolves with no user — are disallowed by default.

func (RemoteConfig) Validate added in v0.66.3

func (r RemoteConfig) Validate() error

Validate checks the [remote] block for static contradictions. Rules are only enforced when Enabled — a disabled block (even with otherwise-invalid values) always loads. It is fail-closed: an invalid enabled block is a hard error.

type RepoConfig added in v0.18.0

type RepoConfig struct {
	Path            string   `toml:"path"`
	AllowConcurrent bool     `toml:"allow_concurrent"`
	Singleton       bool     `toml:"singleton"`
	Includes        []string `toml:"includes"`
}

func (RepoConfig) Validate added in v0.19.0

func (rc RepoConfig) Validate() error

type SandboxConfig added in v0.11.0

type SandboxConfig struct {
	Enabled  bool  `json:"enabled"            toml:"enabled"`
	Disabled *bool `json:"disabled,omitempty" toml:"disabled,omitempty"`
	// Backend selects the sandbox backend: "safehouse" (macOS only) or "nono"
	// (Linux + macOS). It has NO default — when the sandbox is enabled and
	// Backend is unset the daemon fails closed with an actionable error. This
	// is a deliberate pre-1.0 behaviour change (see the nono sandbox design doc).
	Backend string `json:"backend,omitempty" toml:"backend"`
	Command string `json:"command,omitempty" toml:"command"`
	// Profile (nono only) is the base profile graith's generated profile
	// extends. Empty means nono's built-in "default" (its audited deny groups +
	// base system paths). Set it to a maintained registry profile — e.g.
	// "always-further/claude" — to inherit that agent's upstream file grants
	// (its ~/.claude, ~/.claude.json, versioned binary dir, …) instead of
	// hand-listing them via write_files.
	//
	// nono resolves "extends" by MERGING the base profile with graith's
	// generated one. Collection fields (filesystem.allow/read,
	// environment.allow_vars, network.allow_domain, …) are UNIONED (append +
	// dedup) — graith's grants are added to, not substituted for, the base's;
	// only scalar fields (e.g. workdir.access, security.signal_mode) are
	// child-overridden. So graith's filesystem grants are always present, but
	// graith's env allowlist can only WIDEN the base profile's, it cannot narrow
	// it. A base profile that allows extra env vars, network
	// domains, set_vars, command policies, or session hooks (which run outside
	// the sandbox) therefore relaxes graith's baseline — so a custom profile is
	// only as tight as the operator has audited it to be. Choose a trusted,
	// least-privilege profile. nono's audited deny groups (deny_credentials, …)
	// are marked required and merged into every resolved profile regardless of
	// this field, so a custom base cannot silently drop the credential-deny
	// baseline. The safehouse backend has no profile concept and ignores it.
	Profile   string   `json:"profile,omitempty"    toml:"profile"`
	Features  []string `json:"features,omitempty"   toml:"features"`
	ReadDirs  []string `json:"read_dirs,omitempty"  toml:"read_dirs"`
	WriteDirs []string `json:"write_dirs,omitempty" toml:"write_dirs"`
	// ReadFiles / WriteFiles grant access to individual files rather than whole
	// directories. They exist for paths that can't be expressed as a directory
	// grant without over-sharing — most importantly single files that live
	// directly in $HOME (e.g. an agent's ~/.claude.json login file), where
	// granting the parent directory would expose unrelated secrets (.env, ssh
	// keys, tfvars). ReadFiles is read-only; WriteFiles is read+write, mirroring
	// the read_dirs / write_dirs convention (where "write" means read+write, not
	// nono's write-only mode). They map to the nono profile's
	// filesystem.read_file / filesystem.allow_file; the safehouse backend folds
	// them into its read-only / read-write path lists.
	ReadFiles  []string `json:"read_files,omitempty"  toml:"read_files"`
	WriteFiles []string `json:"write_files,omitempty" toml:"write_files"`
	// SignalMode controls whether the sandboxed process may signal other
	// processes. It maps to nono's security.signal_mode ("isolated",
	// "allow_same_sandbox", "allow_all"). Empty inherits nono's base-profile
	// default (allow_same_sandbox). safehouse ignores it. Setting "isolated"
	// makes graith's `process-control` semantics meaningful under nono (Phase 1
	// left it a no-op). See the nono sandbox design doc §C5.
	SignalMode string `json:"signal_mode,omitempty" toml:"signal_mode"`
	// Network is an optional egress policy. It maps to the nono profile's
	// network section (network.block / network.allow_domain). safehouse has no
	// network primitive and only warns. A network policy also raises the
	// enforcement floor: nono needs Landlock ABI v4 (kernel 6.7+) to filter
	// network, so a requested policy on an older kernel fails closed.
	Network *SandboxNetworkConfig `json:"network,omitempty" toml:"network"`
}

func (SandboxConfig) Merge added in v0.11.0

func (s SandboxConfig) Merge(agent SandboxConfig) SandboxConfig

type SandboxNetworkConfig added in v0.64.0

type SandboxNetworkConfig struct {
	// Block denies all outbound network access (nono is network-allowed by
	// default). Maps to network.block = true.
	Block bool `json:"block,omitempty" toml:"block"`
	// AllowDomains is the proxy allowlist. Maps to network.allow_domain. When
	// set, nono runs its L7 filtering proxy and only these domains are
	// reachable. Entries are plain hostnames or URL globs.
	AllowDomains []string `json:"allow_domains,omitempty" toml:"allow_domains"`
}

SandboxNetworkConfig is graith's egress policy. It maps directly onto nono v0.66.0's profile network section: Block -> network.block, AllowDomains -> network.allow_domain (an L7 proxy allowlist; a plain hostname allows the host, a URL glob restricts to matching endpoints).

func (*SandboxNetworkConfig) IsSet added in v0.64.0

func (n *SandboxNetworkConfig) IsSet() bool

IsSet reports whether this network policy requests any egress restriction. A nil or empty config requests nothing (matches nono's allow-by-default).

type StatusBar added in v0.3.0

type StatusBar struct {
	Enabled  bool   `toml:"enabled"`
	Position string `toml:"position"`
}

type StatusConfig added in v0.32.0

type StatusConfig struct {
	TTL string `toml:"ttl"`
}

func (StatusConfig) TTLDuration added in v0.32.0

func (s StatusConfig) TTLDuration() time.Duration

type TemplateVars

type TemplateVars struct {
	Username                 string
	AgentSessionID           string
	SessionName              string
	SessionID                string
	WorktreePath             string
	ForkSourceAgentSessionID string
	Model                    string
}

type UnknownKey added in v0.66.0

type UnknownKey struct {
	// Table is the dotted parent-table path, e.g. "agents.claude.sandbox".
	// Empty for top-level keys.
	Table string
	// Name is the unrecognised leaf key, e.g. "read_dir".
	Name string
	// Suggestion is the closest known key in the same table, or "" if none is
	// close enough to be worth a "did you mean".
	Suggestion string
}

UnknownKey is a config key that graith's schema does not recognise. It is a diagnostic aid (surfaced by `gr doctor`), not a load error: the runtime load stays lenient so an older daemon won't refuse a config written for a newer graith, and a typo silently drops the key rather than bricking startup. See issue #720.

func UnknownKeys added in v0.66.0

func UnknownKeys(path string) ([]UnknownKey, error)

UnknownKeys parses the TOML at path and reports keys that don't map to any field in the Config schema — typos (read_dir vs read_dirs), keys under the wrong table, or options from a newer graith than this binary. Unknown keys are never returned as an error; the returned error is only for a missing, unreadable, or unparseable file.

func (UnknownKey) FullKey added in v0.66.0

func (u UnknownKey) FullKey() string

FullKey renders the key with its table prefix, e.g. "sandbox.read_dir".

type Watcher added in v0.3.0

type Watcher struct {
	// contains filtered or unexported fields
}

func NewWatcher added in v0.3.0

func NewWatcher(path string, onChange func(*Config), log *slog.Logger) *Watcher

func (*Watcher) Run added in v0.3.0

func (w *Watcher) Run(ctx context.Context) error

Jump to

Keyboard shortcuts

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