use super::*;
pub(super) fn parse_value(kind: FieldType, text: &str) -> Result<Value, &'static str> {
if kind == FieldType::Paths {
return Ok(json!(
text.split(';')
.map(str::trim)
.filter(|p| !p.is_empty())
.collect::<Vec<_>>()
));
}
if text.trim().is_empty() && kind != FieldType::Probability {
return Ok(Value::Null);
}
match kind {
FieldType::Number => text
.trim()
.parse::<u64>()
.map(|n| json!(n))
.map_err(|_| "Enter a non-negative whole number, or leave blank for default."),
FieldType::Probability => text
.trim()
.parse::<f64>()
.ok()
.filter(|value| (0.0..=1.0).contains(value))
.map(|value| json!(value))
.ok_or("Enter a number between 0 and 1."),
_ => Ok(json!(text.trim())),
}
}
pub(crate) fn value_text(value: &Value) -> String {
match value {
Value::Null => String::new(),
Value::String(s) => s.clone(),
Value::Array(items) => items
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>()
.join("; "),
_ => value.to_string(),
}
}
pub(super) fn fields(s: &Settings) -> Vec<SettingsField> {
use FieldType::*;
let context = s.context.clone().unwrap_or_default();
let image = &s.tools.view_image;
let mut fields = Vec::new();
macro_rules! field {
($tab:expr, $path:literal, $label:literal, $kind:expr, $value:expr, $help:literal $(, $heading:literal)?) => {{
let value = json!($value);
fields.push(SettingsField {
path: $path,
label: $label,
help: $help,
tab: $tab,
section_heading: None $(.or(Some($heading)))?,
kind: $kind,
original: value.clone(),
value,
});
}};
}
field!(
0,
"agent.compaction.auto.enabled",
"Automatic compaction",
Boolean,
s.compaction.auto.enabled,
"Summarize older turns automatically.",
"Compaction"
);
field!(
0,
"agent.compaction.auto.threshold_percent",
"Auto-compact threshold (%)",
Number,
s.compaction.auto.threshold_percent,
"1–100; blank disables this threshold."
);
field!(
0,
"agent.compaction.auto.threshold_tokens",
"Auto-compact threshold (tokens)",
Number,
s.compaction.auto.threshold_tokens,
"Positive token count; blank disables this threshold."
);
field!(
0,
"agent.compaction.auto.max_compactions_per_run",
"Subagent auto compact limit",
Number,
s.compaction.auto.max_compactions_per_run,
"Automatic compactions per run (including subagents). 0 removes the count cap; blank uses default 4."
);
field!(
1,
"agent.compaction.provider",
"Compaction provider",
Hidden,
s.compaction.provider,
""
);
field!(
1,
"agent.compaction.model",
"Compaction model",
Model,
s.compaction.model,
"Choose an enabled model, or use the active conversation model. Required from another provider when Claude subscription compaction is off.",
"Compaction"
);
field!(
1,
"agent.compaction.claude_subscription.allow_compaction",
"Allow Claude compaction",
Boolean,
s.compaction.claude_subscription.allow_compaction,
"Allow claude-subscription models to compact. Off requires a Compaction model from another provider.",
"Claude subscription (202,000-token ceiling)"
);
field!(
1,
"agent.compaction.claude_subscription.reserve_tokens",
"Compaction reserve (tokens)",
Number,
s.compaction.claude_subscription.reserve_tokens,
"1–201999; default 16384. Subtracted from 202000 when the compaction provider is claude-subscription."
);
field!(
1,
"agent.compaction.claude_subscription.threshold_percent",
"Claude compact threshold (%)",
Number,
s.compaction.claude_subscription.threshold_percent,
"1–100; default 100% of the reduced context limit. Blank disables this trigger; automatic compaction stays enabled."
);
field!(
1,
"agent.compaction.claude_subscription.threshold_tokens",
"Claude compact threshold (tokens)",
Number,
s.compaction.claude_subscription.threshold_tokens,
"Default 202000; capped at the reduced context limit. First token or percent threshold reached wins."
);
field!(
0,
"agent.context.enabled",
"Fallback context budget",
Boolean,
context.enabled,
"Enable fallback context trimming.",
"Fallback context"
);
field!(
0,
"agent.context.max_tokens",
"Context token limit",
Number,
context.max_tokens,
"Fallback context capacity in tokens."
);
field!(
0,
"agent.context.reserve_tokens",
"Reserved output tokens",
Number,
context.reserve_tokens,
"Tokens reserved for output."
);
field!(
0,
"agent.context.keep_recent_tokens",
"Keep recent tokens",
Number,
context.keep_recent_tokens,
"Recent conversation tokens to retain."
);
field!(
1,
"agent.subagents.execution.absolute_paths",
"Allow outside-project paths",
Boolean,
s.tools.subagents.absolute_paths,
"Allow subagents to access paths outside the project.",
"Subagents"
);
field!(
1,
"agent.subagents.execution.max_depth",
"Maximum nesting depth",
Number,
s.tools.subagents.max_depth,
"Maximum nested subagent depth; validated on save."
);
field!(
1,
"agent.side.model",
"Side model",
Model,
s.side.model,
"Choose an enabled model, or inherit the primary provider/model when a side session starts.",
"Side agent"
);
field!(
1,
"agent.side.provider",
"Side provider",
Hidden,
s.side.provider,
""
);
field!(
1,
"agent.summarizer.auto_start",
"Start summarizer automatically",
Boolean,
s.summarizer.auto_start,
"Start session summaries automatically.",
"Summary agent"
);
field!(
1,
"agent.summarizer.model",
"Summary model",
Model,
s.summarizer.model,
"Choose an enabled model, or use the active conversation model."
);
field!(
1,
"agent.summarizer.provider",
"Summary provider",
Hidden,
s.summarizer.provider,
""
);
field!(
1,
"agent.summarizer.reasoning",
"Summary reasoning",
Choice(REASONING_CHOICES),
s.summarizer.reasoning,
"default, low, medium, high, x_high, max; blank uses default."
);
field!(
1,
"sessions.titles.enabled",
"Generate session titles",
Boolean,
s.session_titles.enabled,
"Automatically name sessions.",
"Session titles"
);
field!(
1,
"sessions.titles.model",
"Title model",
Model,
s.session_titles.model,
"Choose an enabled model. Clear only when session titles are disabled."
);
field!(
1,
"sessions.titles.provider",
"Title provider",
Hidden,
s.session_titles.provider,
""
);
field!(
4,
"internal_tooling.jev.enabled",
"Enable Jev tooling",
Boolean,
s.jev.enabled,
"Enable TypeSafe Jev features. Requires TYPESAFE_API_KEY after restart.",
"Jev"
);
field!(
4,
"internal_tooling.jev.show_debug_messages",
" Show Jev debug messages",
Boolean,
s.jev.show_debug_messages,
"Show successful Jev diagnostics. Warnings remain visible when disabled."
);
field!(
4,
"internal_tooling.jev.bash_protection.enabled",
" Enable Bash protection",
Boolean,
s.jev.bash_protection.enabled,
"Used only when Jev tooling is enabled. Score bash commands before execution and request approval for risky commands.",
" Bash protection"
);
field!(
4,
"internal_tooling.jev.bash_protection.protection_level",
" Protection level",
Choice(PROTECTION_LEVEL_CHOICES),
s.jev.bash_protection.protection_level,
"Used only when Jev tooling is enabled. permissive, relaxed, balanced, cautious, or strict."
);
field!(
4,
"internal_tooling.jev.bash_protection.failure_policy",
" API failure policy",
Choice(FAILURE_POLICY_CHOICES),
s.jev.bash_protection.failure_policy,
"Used only when Jev tooling is enabled. auto_approve or auto_deny; defaults to auto_approve."
);
field!(
4,
"internal_tooling.jev.bash_file_tracking.enabled",
" Track Bash file activity",
Boolean,
s.jev.bash_file_tracking.enabled,
"Infer reads, writes, and edits for Session files. Sends bounded, redacted commands to TypeSafe; guesses never grant edit permissions. Requires restart.",
" Bash file tracking"
);
field!(
4,
"internal_tooling.jev.humanize_protection.enabled",
" Enable prose protection",
Boolean,
s.jev.humanize_protection.enabled,
"Used only when Jev tooling is enabled. Check comments and documentation before write and hash_edit calls.",
" Prose protection"
);
field!(
4,
"internal_tooling.jev.humanize_protection.content_threshold",
" Prose detection threshold",
Number,
s.jev.humanize_protection.content_threshold,
"Used only when Jev tooling is enabled. Probability from 0 to 1 required to treat a change as comments or documentation."
);
field!(
4,
"internal_tooling.jev.humanize_protection.humanized_threshold",
" Humanized threshold",
Number,
s.jev.humanize_protection.humanized_threshold,
"Used only when Jev tooling is enabled. Probability from 0 to 1 required to allow comments or documentation."
);
field!(
4,
"internal_tooling.jev.humanize_protection.failure_policy",
" API failure policy",
Choice(FAILURE_POLICY_CHOICES),
s.jev.humanize_protection.failure_policy,
"Used only when Jev tooling is enabled. auto_approve or auto_deny; defaults to auto_deny."
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.enabled",
" Assess tool output",
Boolean,
s.jev.prompt_injection_protection.enabled,
"Assess untrusted tool output for prompt injection. Defaults to observe-only.",
" Prompt injection protection"
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.enforcement_enabled",
" Enforce decisions",
Boolean,
s.jev.prompt_injection_protection.enforcement_enabled,
"Annotate or withhold risky output from provider context. Leave disabled to observe only."
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.protection_level",
" Protection level",
Choice(PROTECTION_LEVEL_CHOICES),
s.jev.prompt_injection_protection.protection_level,
"Choose permissive, relaxed, balanced, cautious, or strict. Balanced is the default."
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.max_assessed_bytes",
" Maximum assessed bytes",
Number,
s.jev.prompt_injection_protection.max_assessed_bytes,
"Bound sent to Jev; truncation appears in diagnostics."
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.failure_policy",
" API failure policy",
Choice(PROMPT_INJECTION_FAILURE_POLICY_CHOICES),
s.jev.prompt_injection_protection.failure_policy,
"annotate or quarantine; defaults to quarantine."
);
field!(
4,
"internal_tooling.jev.prompt_injection_protection.tools",
" Protected tools",
MultiChoice(PROMPT_INJECTION_TOOL_CHOICES),
s.jev.prompt_injection_protection.tools,
"Select tool outputs assessed for prompt injection. Defaults to web."
);
field!(
4,
"internal_tooling.jev.completion_verification.enabled",
" Verify completions",
Boolean,
s.jev.completion_verification.enabled,
"Assess terminal assistant responses with bounded Jev judgments. Defaults to observe-only.",
" Completion verification"
);
field!(
4,
"internal_tooling.jev.completion_verification.enforcement_enabled",
" Enforce completion decisions",
Boolean,
s.jev.completion_verification.enforcement_enabled,
"Allow one bounded corrective continuation or disclose limitations."
);
field!(
4,
"internal_tooling.jev.completion_verification.threshold",
" Completion threshold",
Probability,
s.jev.completion_verification.threshold,
"Probability threshold from 0 to 1 for completion dimensions."
);
field!(
4,
"internal_tooling.jev.completion_verification.max_state_bytes",
" Maximum evidence bytes",
Number,
s.jev.completion_verification.max_state_bytes,
"Maximum bounded evidence sent to Jev."
);
field!(
4,
"internal_tooling.jev.completion_verification.max_continuations",
" Maximum continuations",
Number,
s.jev.completion_verification.max_continuations,
"Maximum verifier-triggered continuations per user turn."
);
field!(
4,
"internal_tooling.jev.completion_verification.failure_policy",
" Verifier failure policy",
Choice(COMPLETION_VERIFICATION_FAILURE_POLICY_CHOICES),
s.jev.completion_verification.failure_policy,
"accept_with_warning or reject_with_warning."
);
field!(
4,
"internal_tooling.jev.skill_suggestion.enabled",
" Suggest skills",
Boolean,
s.jev.skill_suggestion.enabled,
"Used only when Jev tooling is enabled. Before each typed prompt, Jev ranks skills and adds one hint naming the best fit. Off sends requests unchanged.",
" Skill suggestion"
);
field!(
4,
"internal_tooling.jev.skill_suggestion.gate_threshold",
" Skill need threshold",
Number,
s.jev.skill_suggestion.gate_threshold,
"Probability from 0 to 1 that a prompt needs any skill before candidates are checked."
);
field!(
4,
"internal_tooling.jev.skill_suggestion.fit_threshold",
" Skill fit threshold",
Number,
s.jev.skill_suggestion.fit_threshold,
"Probability from 0 to 1 that the best candidate fits before it is suggested."
);
field!(
4,
"internal_tooling.jev.compaction_timing.enabled",
" Compact at task boundaries",
Boolean,
s.jev.compaction_timing.enabled,
"Used only when Jev tooling and auto-compaction are enabled. Between the soft threshold and the auto-compaction threshold, compact early when Jev judges the new prompt starts unrelated work. Off keeps current timing.",
" Compaction timing"
);
field!(
4,
"internal_tooling.jev.compaction_timing.soft_threshold_percent",
" Soft threshold percent",
Number,
s.jev.compaction_timing.soft_threshold_percent,
"Context percentage from 1 to 99 where early compaction checks begin."
);
field!(
4,
"internal_tooling.jev.compaction_timing.threshold",
" Task boundary threshold",
Number,
s.jev.compaction_timing.threshold,
"Probability from 0 to 1 required to compact early."
);
field!(
4,
"internal_tooling.jev.reset_cache",
"Reset Jev cache",
Action,
"Reset",
"Delete all cached Jev responses. New requests will call TypeSafe again."
);
field!(
5,
"automation.integrations.herdr.enabled",
"Herdr integration",
Boolean,
s.integrations.herdr.enabled,
"Enable Herdr integration after restart.",
"Integrations"
);
field!(
3,
"capabilities.tools.manage",
"Enable/disable tools",
Action,
"Open",
"Choose which built-in, Code Mode, and MCP tools are available.",
"Tool availability"
);
field!(
3,
"capabilities.tools.view_image.absolute_paths",
"Images outside project",
Boolean,
image.absolute_paths,
"Allow image reads outside the project.",
"View image"
);
field!(
3,
"capabilities.tools.view_image.max_image_bytes",
"Maximum image bytes",
Number,
image.max_image_bytes,
"Positive byte limit; validated on save."
);
field!(
3,
"capabilities.tools.view_image.vision_model.provider",
"Image provider",
Hidden,
image.vision_model.as_ref().map(|m| &m.provider),
""
);
field!(
3,
"capabilities.tools.view_image.vision_model.model",
"Image model",
Model,
image.vision_model.as_ref().map(|m| &m.model),
"Choose an enabled model, or clear the image model override."
);
field!(
3,
"capabilities.tools.code_mode.compact_direct_tools",
"Compact direct tools",
Boolean,
s.tools.code_mode.compact_direct_tools,
"Hide inventory and MCP tools from direct calls; requires Code Mode.",
"Code Mode"
);
field!(
3,
"capabilities.tools.code_mode.nudges_enabled",
"Suggest mechanical batches",
Boolean,
s.tools.code_mode.nudges_enabled,
"At most one advisory hint per turn; never forces batching."
);
field!(
3,
"capabilities.tools.code_mode.manage",
"Manage Code Mode tools",
Action,
Value::Null,
"Choose tools available inside Code Mode, independently of direct tools."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_source_bytes",
"Maximum source bytes",
Number,
s.tools.code_mode.limits.max_source_bytes,
"1 to 1048576 UTF-8 bytes of JavaScript source."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_inner_calls",
"Maximum inner calls",
Number,
s.tools.code_mode.limits.max_inner_calls,
"1 to 1000 synchronous calls per batch."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_runtime_ms",
"JavaScript time budget (milliseconds)",
Number,
s.tools.code_mode.limits.max_runtime_ms,
"1 to 3600000 milliseconds; excludes host-tool wait time."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_memory_bytes",
"Sandbox memory bytes",
Number,
s.tools.code_mode.limits.max_memory_bytes,
"1048576 to 268435456 bytes; exhausting memory terminates the batch."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_inner_result_bytes",
"Maximum SDK response bytes",
Number,
s.tools.code_mode.limits.max_inner_result_bytes,
"1 to 16777216 bytes, including discovery responses."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_total_result_bytes",
"Aggregate SDK response bytes",
Number,
s.tools.code_mode.limits.max_total_result_bytes,
"1 to 67108864 bytes per batch, including discovery."
);
field!(
3,
"capabilities.tools.code_mode.limits.max_return_bytes",
"Maximum returned bytes",
Number,
s.tools.code_mode.limits.max_return_bytes,
"1 to 4194304 JSON bytes; host warnings remain separate."
);
field!(
5,
"interface.tui.subagent_card_rows",
"Subagent card rows",
Number,
s.tui.subagent_card_rows,
"1–50 rows; blank uses default.",
"Appearance"
);
field!(
5,
"interface.tui.autocomplete.respects_gitignore",
"Autocomplete respects gitignore",
Boolean,
s.file_autocomplete_respects_gitignore,
"Hide ignored files from file completion."
);
field!(
0,
"knowledge.skills.additional_paths",
"Additional skill directories",
Paths,
s.skills.additional_paths,
"Separate paths with semicolons; blank clears the list.",
"Skill directories"
);
field!(
1,
"providers.streaming.semantic_progress_timeout_seconds",
"Main stream timeout (seconds)",
Number,
s.provider_stream.semantic_progress_timeout_seconds,
"Seconds without semantic progress; blank uses default.",
"Streaming"
);
field!(
1,
"providers.streaming.subagent_semantic_progress_timeout_seconds",
"Subagent stream timeout (seconds)",
Number,
s.provider_stream.subagent_semantic_progress_timeout_seconds,
"Seconds without semantic progress; blank uses default."
);
field!(
3,
"capabilities.tools.magi_control.compact",
"Allow agent-requested compaction",
Boolean,
s.tools.magi_control.compact,
"Allow compact through magi_control. Context usage remains available; disable the whole tool through tool availability.",
"Magi control"
);
fields
}