use newt_core::interaction_surface::SurfaceInteraction;
use newt_core::settings_receipt::SettingChange;
use newt_core::HumanQuestionOutcome;
use newt_interaction::InteractionDefinition;
pub(crate) type Ask<'a> = &'a dyn Fn(&SurfaceInteraction) -> HumanQuestionOutcome;
pub(crate) enum ValueSpace {
Choice(Vec<(&'static str, String)>),
Number {
release: &'static str,
min: usize,
max: usize,
},
Text {
release: &'static str,
placeholder: &'static str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Field {
EditMode,
Tenacity,
Initiative,
Cognition,
Thinking,
Nudge,
Markdown,
Mode,
Prompt,
SystemPrompt,
Compaction,
Detail,
Posture,
Rounds,
Prefix,
GitAuthor,
GitConfig,
GitSigning,
Tools,
ReadOnly,
}
impl Field {
pub(crate) const ALL: &'static [Self] = &[
Self::EditMode,
Self::Tenacity,
Self::Initiative,
Self::Cognition,
Self::Thinking,
Self::Nudge,
Self::Markdown,
Self::Mode,
Self::Prompt,
Self::SystemPrompt,
Self::Compaction,
Self::Detail,
Self::Posture,
Self::Rounds,
Self::Prefix,
Self::GitAuthor,
Self::GitConfig,
Self::GitSigning,
Self::Tools,
Self::ReadOnly,
];
pub(crate) fn name(self) -> &'static str {
match self {
Self::EditMode => "edit-mode",
Self::Tenacity => "tenacity",
Self::Initiative => "initiative",
Self::Cognition => "cognition",
Self::Thinking => "thinking",
Self::Nudge => "nudge",
Self::Markdown => "markdown",
Self::Mode => "mode",
Self::Prompt => "prompt",
Self::SystemPrompt => "system-prompt",
Self::Compaction => "compaction",
Self::Detail => "detail",
Self::Posture => "posture",
Self::Rounds => "rounds",
Self::Prefix => "prefix",
Self::GitAuthor => "git-author",
Self::GitConfig => "git-config",
Self::GitSigning => "git-signing",
Self::Tools => "tools",
Self::ReadOnly => "read-only",
}
}
pub(crate) fn label(self) -> &'static str {
match self {
Self::EditMode => "line-editor key bindings",
Self::Tenacity => "tenacity",
Self::Initiative => "initiative",
Self::Cognition => "cognition",
Self::Thinking => "reasoning display",
Self::Nudge => "action-pressure nudges",
Self::Markdown => "markdown rendering",
Self::Mode => "operating mode",
Self::Prompt => "input prompt template",
Self::SystemPrompt => "system prompt file",
Self::Compaction => "automatic-compaction trigger",
Self::Detail => "tool-output detail rows",
Self::Posture => "permission posture",
Self::Rounds => "tool-call round limit",
Self::Prefix => "meta prefix key",
Self::GitAuthor => "sandbox git commit author",
Self::GitConfig => "sandbox git settings",
Self::GitSigning => "harness commit signing",
Self::Tools => "tools the model sees",
Self::ReadOnly => "hide the write tools",
}
}
pub(crate) fn value_space(self) -> ValueSpace {
use newt_core::role_profile::Cognition;
use newt_core::{Initiative, Tenacity};
let owned = |pairs: &[(&'static str, &str)]| -> ValueSpace {
ValueSpace::Choice(pairs.iter().map(|(v, d)| (*v, (*d).to_string())).collect())
};
match self {
Self::EditMode => owned(&[
("vi", "modal editing (default)"),
("emacs", "emacs-style editing"),
("nano", "nano-style editing"),
]),
Self::Tenacity => ValueSpace::Choice(
std::iter::once(("auto", "normal, unless set explicitly".to_string()))
.chain(
Tenacity::all()
.into_iter()
.map(|t| (t.label(), t.describe())),
)
.collect(),
),
Self::Initiative => ValueSpace::Choice(
std::iter::once((
"auto",
"inherit from the persona / config / model family".to_string(),
))
.chain(
Initiative::all()
.into_iter()
.map(|i| (i.label(), i.describe())),
)
.collect(),
),
Self::Cognition => ValueSpace::Choice(
std::iter::once(("auto", "follow the active persona (default)".to_string()))
.chain(std::iter::once((
"off",
"send no reasoning controls, overriding any persona".to_string(),
)))
.chain(
Cognition::all()
.into_iter()
.map(|c| (c.label(), c.describe().to_string())),
)
.collect(),
),
Self::Thinking => owned(&[
(
"fold",
"bounded reasoning, then how long it thought (default)",
),
("stream", "every reasoning line, unbounded"),
("off", "just the answer"),
]),
Self::Nudge => owned(&[
("on", "action-pressure steering enabled (default)"),
("off", "no narration rescue / workflow repair / plan pushes"),
]),
Self::GitAuthor => owned(&[
(
"agent",
"the agent identity, so sandbox commits never pass for yours (default)",
),
("operator", "your own git name and email"),
]),
Self::GitConfig => owned(&[
("baseline", "git's own defaults; nothing copied (default)"),
(
"environment",
"copy your ~/.gitconfig's plain settings now (never aliases, credentials or includes)",
),
]),
Self::GitSigning => owned(&[
("off", "newt's commits are unsigned (default)"),
(
"harness",
"a harness key newt holds (created if `signing_key` is unset)",
),
(
"operator",
"your own git signing key (user.signingkey, gpg.format)",
),
]),
Self::Prefix => owned(&[
("ctrl+space", "collides with nothing in newt (default)"),
(
"ctrl+a",
"GNU Screen's; shadows line-start in emacs/nano editing",
),
(
"ctrl+b",
"tmux's and herdr's own; inside them it reaches them first",
),
("ctrl+n", "shadows next-line in emacs editing"),
]),
Self::Markdown => owned(&[
("auto", "render when colour is active (default)"),
("on", "always render (still needs colour)"),
("off", "stream raw text"),
]),
Self::Mode => ValueSpace::Choice(
newt_core::operating_mode::OperatingMode::all()
.iter()
.map(|m| (m.as_str(), m.description().to_string()))
.collect(),
),
Self::Compaction => ValueSpace::Choice(vec![
(
newt_core::CompactionTriggerPolicy::HeadroomAware.keyword(),
"defer a count-only compaction while input headroom is known (default)"
.to_string(),
),
(
newt_core::CompactionTriggerPolicy::MessageCount.keyword(),
"compact whenever the message count exceeds its threshold".to_string(),
),
]),
Self::Prompt => ValueSpace::Text {
release: "reset",
placeholder: "\"[$TIME] ❯ \"",
},
Self::SystemPrompt => ValueSpace::Text {
release: "reset",
placeholder: "~/.newt/system-prompt.md | edit",
},
Self::Tools => ValueSpace::Text {
release: "all",
placeholder: "pi or none or read_file,run_command,find",
},
Self::ReadOnly => owned(&[
(
"on",
"hide write_file / edit_file / delete_file and clamp fs_write with them",
),
("off", "the write tools follow the allow list"),
]),
Self::Posture => {
let mut options = vec![("off", "no posture — release the clamp".to_string())];
if let Ok(cfg) =
crate::migration_notices::read(|report| newt_core::Config::resolve(report))
{
for (name, m) in &cfg.modes {
let what = m.preset.as_ref().map_or_else(
|| "guidance only (no permission clamp)".to_string(),
|p| format!("clamped by preset '{p}'"),
);
options.push((Box::leak(name.clone().into_boxed_str()) as &str, what));
}
}
ValueSpace::Choice(options)
}
Self::Detail => ValueSpace::Number {
release: "auto",
min: 0,
max: 100_000,
},
Self::Rounds => ValueSpace::Number {
release: "auto",
min: 1,
max: newt_core::tenacity::RELENTLESS_TOOL_ROUND_TARGET,
},
}
}
pub(crate) fn current(self) -> String {
use newt_core::cognition::cli_cognition;
match self {
Self::Prefix => prefix_setting(),
Self::GitAuthor => git_profile().author.as_str().to_string(),
Self::GitConfig => git_profile().config_source.as_str().to_string(),
Self::GitSigning => git_profile().signing.as_str().to_string(),
Self::EditMode => match crate::prompt::resolve_edit_mode() {
newt_core::EditMode::Vi => "vi",
newt_core::EditMode::Emacs => "emacs",
newt_core::EditMode::Nano => "nano",
}
.to_string(),
Self::Tenacity => tenacity_token(newt_core::tenacity::cli_tenacity()),
Self::Initiative => initiative_token(newt_core::initiative::cli_initiative()),
Self::Cognition => cognition_token(cli_cognition()),
Self::Thinking => {
match crate::migration_notices::read(newt_core::agentic::thinking_mode) {
newt_core::ThinkingMode::Fold => "fold",
newt_core::ThinkingMode::Stream => "stream",
newt_core::ThinkingMode::Off => "off",
}
.to_string()
}
Self::Nudge => if nudges_off() { "off" } else { "on" }.to_string(),
Self::Markdown => {
crate::migration_notices::read(newt_core::config::session_markdown_mode)
.keyword()
.to_string()
}
Self::Mode => newt_core::operating_mode::session_operating_mode()
.as_str()
.to_string(),
Self::Prompt => crate::prompt::active_prompt_template(),
Self::SystemPrompt => crate::migration_notices::read(|report| {
newt_core::Config::resolve_unpublished(report)
})
.ok()
.and_then(|cfg| cfg.system_prompt_file())
.map_or_else(|| "reset".to_string(), |p| p.display().to_string()),
Self::Compaction => {
crate::migration_notices::read(newt_core::config::session_compaction_trigger_policy)
.keyword()
.to_string()
}
Self::Detail => newt_core::config::session_spill_lines()
.map_or_else(|| "auto".to_string(), |n| n.to_string()),
Self::Posture => {
newt_core::posture::active_posture().map_or_else(|| "off".to_string(), |p| p.name)
}
Self::Rounds => newt_core::tenacity::session_tool_rounds()
.map_or_else(|| "auto".to_string(), |n| n.to_string()),
Self::Tools => tools_token(&tool_exposure_setting()),
Self::ReadOnly => if tool_exposure_setting().read_only {
"on"
} else {
"off"
}
.to_string(),
}
}
fn value_now(self) -> newt_core::settings_receipt::SettingValue {
use newt_core::settings_receipt::SettingValue;
match self {
Self::Rounds => newt_core::tenacity::session_tool_round_limit()
.map_or_else(|| SettingValue::Token(self.current()), SettingValue::from),
_ => SettingValue::Token(self.current()),
}
}
pub(crate) fn takes_free_text(self) -> bool {
matches!(self.value_space(), ValueSpace::Text { .. })
}
pub(crate) fn from_token(token: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|f| f.name().eq_ignore_ascii_case(token))
}
pub(crate) fn accepts(self, value: &str) -> Option<String> {
let want = value.trim().to_lowercase();
if matches!(self, Self::Mode) {
return newt_core::operating_mode::OperatingMode::from_keyword(&want)
.map(|m| m.as_str().to_string());
}
let want = match (self, want.as_str()) {
(Self::EditMode, "vim") => "vi",
(Self::Tenacity | Self::Initiative, "inherit" | "reset") => "auto",
(Self::Thinking, "on") => "fold",
(Self::Cognition, "none") => "off",
(Self::Cognition, "reset" | "persona") => "auto",
(Self::Rounds, "reset" | "default") => "auto",
(Self::Detail, "reset" | "default" | "config") => "auto",
(Self::Detail, "full" | "unbounded" | "all") => "0",
(Self::Markdown, "always") => "on",
(Self::Markdown, "never") => "off",
_ => want.as_str(),
};
match self.value_space() {
ValueSpace::Choice(values) => values
.into_iter()
.find(|(v, _)| *v == want)
.map(|(v, _)| v.to_string()),
ValueSpace::Text { release, .. } => {
let raw = crate::prompt::strip_one_quote_pair(value);
if matches!(self, Self::Tools) && raw.trim().is_empty() {
return Some(raw.to_string());
}
if raw.trim().eq_ignore_ascii_case(release) || raw.trim().is_empty() {
Some(release.to_string())
} else {
Some(raw.to_string())
}
}
ValueSpace::Number { release, min, max } => {
if want == release {
return Some(release.to_string());
}
want.parse::<usize>()
.ok()
.filter(|n| (min..=max).contains(n))
.map(|n| n.to_string())
}
}
}
#[cfg(test)]
fn offered(self) -> Vec<&'static str> {
match self.value_space() {
ValueSpace::Choice(values) => values.into_iter().map(|(v, _)| v).collect(),
ValueSpace::Number { release, .. } | ValueSpace::Text { release, .. } => {
vec![release]
}
}
}
pub(crate) fn accepts_hint(self) -> String {
match self.value_space() {
ValueSpace::Choice(values) => values
.into_iter()
.map(|(v, _)| v)
.collect::<Vec<_>>()
.join(", "),
ValueSpace::Number { release, min, max } => {
format!("{release}, or a number from {min} to {max}")
}
ValueSpace::Text {
release,
placeholder,
} => format!("{release}, or a template like {placeholder}"),
}
}
}
pub(crate) fn nudges_off() -> bool {
std::env::var("NEWT_NUDGE").is_ok_and(|v| v.eq_ignore_ascii_case("off"))
}
pub(crate) fn field_menu() -> InteractionDefinition {
let entries: Vec<(String, String)> = Field::ALL
.iter()
.enumerate()
.map(|(i, f)| {
(
(i + 1).to_string(),
format!("{} — currently {}", f.label(), f.current()),
)
})
.collect();
let refs: Vec<(&str, &str)> = entries
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
newt_core::interaction_form::menu("settings", "Esc cancels", &refs)
}
pub(crate) fn value_menu(field: Field) -> InteractionDefinition {
let current = field.current();
let ValueSpace::Choice(values) = field.value_space() else {
return newt_core::interaction_form::text_field(
format!("{} — currently {current}", field.label()),
format!("{} · Esc cancels", field.accepts_hint()),
);
};
let entries: Vec<(String, String)> = values
.into_iter()
.enumerate()
.map(|(i, (value, what))| {
let mark = if value == current { " (current)" } else { "" };
((i + 1).to_string(), format!("{value} — {what}{mark}"))
})
.collect();
let refs: Vec<(&str, &str)> = entries
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
newt_core::interaction_form::menu(field.label(), "Esc cancels", &refs)
}
pub(crate) fn prefix_setting() -> String {
std::env::var("NEWT_PREFIX_KEY")
.ok()
.filter(|value| !value.trim().is_empty())
.or_else(|| {
crate::migration_notices::read(|report| newt_core::Config::resolve_unpublished(report))
.ok()
.and_then(|c| c.tui)
.and_then(|t| t.prefix_key)
})
.unwrap_or_else(|| "ctrl+space".to_string())
}
fn persist_tui_key(key: &str, value: &str) -> Result<(), String> {
persist_config(|text| newt_core::Config::with_tui_key(text, key, value))
}
fn persist_config(
rewrite: impl FnOnce(&str) -> Result<String, newt_core::NewtError>,
) -> Result<(), String> {
let path = newt_core::Config::user_config_path()
.ok_or_else(|| "no config directory to save the setting in".to_string())?;
let text = config_fs::read(&path);
let updated = rewrite(&text).map_err(|e| e.to_string())?;
config_fs::write(&path, &updated).map_err(|e| format!("saving config: {e}"))
}
pub(crate) fn tool_exposure_setting() -> newt_core::ToolExposureConfig {
crate::migration_notices::read(|report| newt_core::Config::resolve_unpublished(report))
.map(|c| c.tool_exposure())
.unwrap_or_default()
}
#[cfg(not(test))]
fn effective_exposure() -> newt_core::ToolExposureConfig {
tool_exposure_setting()
}
#[cfg(test)]
thread_local! {
pub(crate) static SHADOWED_EXPOSURE: std::cell::RefCell<Option<newt_core::ToolExposureConfig>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(test)]
fn effective_exposure() -> newt_core::ToolExposureConfig {
if std::env::var_os(config_fs::REAL_LAYERS).is_some() {
return tool_exposure_setting();
}
SHADOWED_EXPOSURE
.with(|s| s.borrow().clone())
.unwrap_or_else(|| {
let path = newt_core::Config::user_config_path().expect("a config path");
toml::from_str::<newt_core::Config>(&config_fs::read(&path))
.map(|c| c.tool_exposure())
.unwrap_or_default()
})
}
fn confirm_in_force(
key: &str,
took: impl Fn(&newt_core::ToolExposureConfig) -> bool,
label: &str,
) -> Result<(), String> {
if took(&effective_exposure()) {
return Ok(());
}
let layer = overriding_layer(&layers_above_user_file(), key, &took)
.unwrap_or_else(|| "another config layer".to_string());
Err(format!(
"{label}: saved to your config, but {layer} overrides it, so it is NOT in force \
— change it there"
))
}
fn layers_above_user_file() -> Vec<(String, String, bool)> {
let read = |path: &std::path::Path| std::fs::read_to_string(path).unwrap_or_default();
let pinned = newt_core::Config::pinned_config_path();
let mut layers = Vec::new();
if let Some(project) =
newt_core::Config::project_config_path().filter(|p| Some(p) != pinned.as_ref())
{
layers.push((
format!("the project config {}", project.display()),
read(&project),
false,
));
}
if let Some(pinned) = pinned {
layers.push((
"the config pinned by NEWT_CONFIG".to_string(),
read(&pinned),
true,
));
}
layers
}
fn overriding_layer(
layers: &[(String, String, bool)],
key: &str,
took: &dyn Fn(&newt_core::ToolExposureConfig) -> bool,
) -> Option<String> {
layers.iter().find_map(|(label, text, is_base)| {
let sets_key = text
.parse::<toml::Table>()
.ok()?
.get("tool_exposure")
.and_then(toml::Value::as_table)
.is_some_and(|t| t.contains_key(key));
let exposure = toml::from_str::<newt_core::Config>(text)
.ok()?
.tool_exposure();
((sets_key || *is_base) && !took(&exposure)).then(|| label.clone())
})
}
pub(crate) fn tools_token(exposure: &newt_core::ToolExposureConfig) -> String {
match &exposure.allow_list {
None => "all".to_string(),
Some(list) if list.is_empty() => "none".to_string(),
Some(_) if exposure.is_pi_preset() => "pi".to_string(),
Some(list) => list.join(","),
}
}
fn parse_tools_token(value: &str) -> Result<Option<Vec<String>>, String> {
match crate::prompt::strip_one_quote_pair(value).trim() {
"" => Err("no tools given — use `none` for no tools or `all` to clear the list".into()),
"all" => Ok(None),
"none" => Ok(Some(Vec::new())),
"pi" => Ok(Some(
newt_core::PI_PRESET_TOOLS
.iter()
.map(|s| s.to_string())
.collect(),
)),
list => {
let names: Vec<String> = list
.split([',', ' '])
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
if names.is_empty() {
return Err("no tool names — use `none` for no tools, `all` for every tool".into());
}
let known = newt_core::agentic::builtin_tool_names();
let unknown: Vec<&str> = names
.iter()
.map(String::as_str)
.filter(|n| !n.contains("__") && !known.contains(n))
.collect();
if unknown.is_empty() {
Ok(Some(names))
} else {
Err(format!(
"unknown tool{} {} — the catalog has: {}",
if unknown.len() == 1 { "" } else { "s" },
unknown.join(", "),
known.join(", ")
))
}
}
}
}
#[cfg(not(test))]
mod config_fs {
pub(super) fn read(path: &std::path::Path) -> String {
std::fs::read_to_string(path).unwrap_or_default()
}
pub(super) fn write(path: &std::path::Path, text: &str) -> std::io::Result<()> {
std::fs::write(path, text)
}
}
#[cfg(test)]
pub(crate) mod config_fs {
use std::cell::RefCell;
use std::path::{Path, PathBuf};
thread_local! {
static FILES: RefCell<Vec<(PathBuf, String)>> = const { RefCell::new(Vec::new()) };
}
pub(crate) const REAL_LAYERS: &str = "NEWT_SETTINGS_REAL_LAYERS";
pub(crate) fn read(path: &Path) -> String {
if std::env::var_os(REAL_LAYERS).is_some() {
return std::fs::read_to_string(path).unwrap_or_default();
}
FILES.with(|files| {
files
.borrow()
.iter()
.rev()
.find(|(p, _)| p == path)
.map(|(_, text)| text.clone())
.unwrap_or_default()
})
}
pub(crate) fn write(path: &Path, text: &str) -> std::io::Result<()> {
if std::env::var_os(REAL_LAYERS).is_some() {
return std::fs::write(path, text);
}
FILES.with(|files| {
files
.borrow_mut()
.push((path.to_path_buf(), text.to_string()));
});
Ok(())
}
}
fn apply(field: Field, value: &str) -> Result<String, String> {
use newt_core::cognition::CognitionOverride;
use newt_core::role_profile::Cognition;
use newt_core::{Initiative, Tenacity};
let Some(value) = field.accepts(value) else {
return Err(format!(
"/settings {} takes {}",
field.name(),
field.accepts_hint()
));
};
let value = value.as_str();
match field {
Field::EditMode => newt_core::process_env::set_var("NEWT_EDIT_MODE", value),
Field::Tenacity => write_tenacity(value.parse::<Tenacity>().ok()),
Field::Initiative => write_initiative(value.parse::<Initiative>().ok()),
Field::Cognition => write_cognition(match value {
"auto" => CognitionOverride::Unset,
"off" => CognitionOverride::Off,
level => level
.parse::<Cognition>()
.map_or(CognitionOverride::Unset, CognitionOverride::Set),
}),
Field::Thinking => newt_core::process_env::set_var("NEWT_THINKING", value),
Field::Markdown => newt_core::process_env::set_var("NEWT_MARKDOWN", value),
Field::Mode => {
if let Some(mode) = newt_core::operating_mode::OperatingMode::from_keyword(value) {
newt_core::operating_mode::set_session_operating_mode(mode);
}
}
Field::Nudge => {
if value == "off" {
newt_core::process_env::set_var("NEWT_NUDGE", "off");
} else {
newt_core::process_env::remove_var("NEWT_NUDGE");
}
}
Field::Rounds => newt_core::tenacity::set_session_tool_rounds(value.parse::<usize>().ok()),
Field::Posture => {
if value == "off" {
newt_core::posture::set_active_posture(None);
} else {
let cfg =
crate::migration_notices::read(|report| newt_core::Config::resolve(report))
.map_err(|e| e.to_string())?;
let dirs = cfg.skill_search_dirs();
let posture = newt_core::posture::build_posture(value, &cfg, |skill| {
newt_skills::load_body_from(&dirs, skill)
})
.map_err(|e| e.to_string())?;
newt_core::posture::set_active_posture(Some(posture));
}
}
Field::Detail => {
newt_core::config::set_session_spill_lines(value.parse::<usize>().ok());
}
Field::Compaction => {
if let Some(policy) = newt_core::CompactionTriggerPolicy::from_keyword(value) {
newt_core::process_env::set_var("NEWT_COMPACTION_TRIGGER", policy.keyword());
}
}
Field::Prompt => {
if value == "reset" {
newt_core::process_env::remove_var("NEWT_PROMPT");
} else {
newt_core::process_env::set_var("NEWT_PROMPT", value);
}
}
Field::SystemPrompt => match value {
"edit" => return Err("system-prompt edit needs the interactive session".to_string()),
"reset" => newt_core::process_env::set_var(newt_core::SYSTEM_PROMPT_FILE_ENV, ""),
path => {
newt_core::SystemPromptOverride::read_typed(path).map_err(|e| {
format!("{e}; nothing changed, newt keeps sending its previous prompt")
})?;
newt_core::process_env::set_var(newt_core::SYSTEM_PROMPT_FILE_ENV, path);
}
},
Field::GitAuthor => persist_git_profile(|git| {
git.author = if value == "operator" {
newt_core::agent_identity::SandboxAuthor::Operator
} else {
newt_core::agent_identity::SandboxAuthor::Agent
};
Ok(())
})?,
Field::GitConfig => persist_git_profile(|git| {
if value == "environment" {
let listing = newt_core::git_hardening::ambient_git_config_listing()
.map_err(|e| format!("could not read your git config: {e}"))?;
git.config = newt_core::git_hardening::copyable_git_config(&listing);
git.config_source = newt_core::agent_identity::GitConfigSource::Environment;
} else {
git.config.clear();
git.config_source = newt_core::agent_identity::GitConfigSource::Baseline;
}
Ok(())
})?,
Field::GitSigning => {
let mut created = None;
persist_identity(|identity| {
use newt_core::agent_identity::SigningMode;
identity.git.signing = match value {
"harness" => SigningMode::Harness,
"operator" => SigningMode::Operator,
_ => SigningMode::Off,
};
if value == "harness" && identity.signing_key.is_none() {
let dir = newt_core::Config::user_config_dir()
.ok_or("no config directory for the harness key")?;
let path = dir.join("harness-signing.pem");
created = Some(newt_core::commit_signing::generate_harness_key(&path)?);
identity.signing_key = Some(path.to_string_lossy().into_owned());
}
Ok(())
})?;
if let Some(public) = created {
return Ok(format!(
"{}: {value}\nnew harness key; add this to the agent's GitHub account \
as a signing key so its commits verify:\n{public}",
field.label()
));
}
}
Field::Prefix => {
persist_tui_key("prefix_key", value)?;
newt_core::process_env::set_var("NEWT_PREFIX_KEY", value);
#[cfg(feature = "rich-tui")]
crate::prefix::invalidate();
}
Field::Tools => {
let list = parse_tools_token(value)?;
let names: Vec<&str> = list.iter().flatten().map(String::as_str).collect();
persist_config(|text| {
newt_core::Config::with_tool_exposure_allow_list(
text,
list.as_ref().map(|_| names.as_slice()),
)
})?;
confirm_in_force("allow_list", |now| now.allow_list == list, field.label())?;
}
Field::ReadOnly => {
persist_config(|text| {
newt_core::Config::with_tool_exposure_read_only(text, value == "on")
})?;
confirm_in_force(
"read_only",
|now| now.read_only == (value == "on"),
field.label(),
)?;
}
}
Ok(format!("{}: {value}", field.label()))
}
fn git_profile() -> newt_core::agent_identity::GitProfile {
newt_core::AgentIdentity::resolve().unwrap_or_default().git
}
fn persist_git_profile(
edit: impl FnOnce(&mut newt_core::agent_identity::GitProfile) -> Result<(), String>,
) -> Result<(), String> {
persist_identity(|identity| edit(&mut identity.git))
}
fn persist_identity(
edit: impl FnOnce(&mut newt_core::AgentIdentity) -> Result<(), String>,
) -> Result<(), String> {
let path = newt_core::AgentIdentity::user_identity_path()
.ok_or("no home directory to hold agent-identity.toml")?;
let mut identity = if path.is_file() {
newt_core::AgentIdentity::load(&path).map_err(|e| e.to_string())?
} else {
newt_core::AgentIdentity::default()
};
edit(&mut identity)?;
identity.save(&path).map_err(|e| e.to_string())
}
fn change_for(
setting: &str,
from: newt_core::settings_receipt::SettingValue,
to: newt_core::settings_receipt::SettingValue,
via: &str,
) -> Option<SettingChange> {
match crate::slash_registry::receipt_for(setting) {
crate::slash_registry::Receipt::Journal => Some(SettingChange::new(setting, from, to, via)),
_ => None,
}
}
pub(crate) fn apply_and_record(field: Field, value: &str, via: &str) -> Result<String, String> {
recorded(field, via, || apply(field, value)).map(|(message, _)| message)
}
fn recorded<T>(
field: Field,
via: &str,
write: impl FnOnce() -> Result<T, String>,
) -> Result<(T, Option<SettingChange>), String> {
recorded_values(field.name(), field.value_now(), via, || {
let applied = write()?;
Ok((applied, field.value_now()))
})
}
fn recorded_values<T>(
setting: &str,
from: newt_core::settings_receipt::SettingValue,
via: &str,
write: impl FnOnce() -> Result<(T, newt_core::settings_receipt::SettingValue), String>,
) -> Result<(T, Option<SettingChange>), String> {
let (applied, to) = write()?;
let change = change_for(setting, from, to, via);
if let Some(change) = change.clone() {
let _ = newt_core::settings_receipt::record(change);
}
Ok((applied, change))
}
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_cognition(
choice: newt_core::cognition::CognitionOverride,
via: &str,
) -> String {
journalled_cognition(choice, via).0
}
#[cfg(feature = "rich-tui")]
fn journalled_cognition(
choice: newt_core::cognition::CognitionOverride,
via: &str,
) -> (String, Option<SettingChange>) {
recorded(Field::Cognition, via, || {
write_cognition(choice);
Ok(format!(
"{}: {}",
Field::Cognition.label(),
Field::Cognition.current()
))
})
.unwrap_or_else(|never| (never, None))
}
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_tenacity(level: Option<newt_core::Tenacity>, via: &str) -> String {
journalled_tenacity(level, via).0
}
#[cfg(feature = "rich-tui")]
fn journalled_tenacity(
level: Option<newt_core::Tenacity>,
via: &str,
) -> (String, Option<SettingChange>) {
recorded(Field::Tenacity, via, || {
write_tenacity(level);
Ok(format!(
"{}: {}",
Field::Tenacity.label(),
Field::Tenacity.current()
))
})
.unwrap_or_else(|never| (never, None))
}
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_initiative(level: Option<newt_core::Initiative>, via: &str) -> String {
journalled_initiative(level, via).0
}
#[cfg(feature = "rich-tui")]
fn journalled_initiative(
level: Option<newt_core::Initiative>,
via: &str,
) -> (String, Option<SettingChange>) {
recorded(Field::Initiative, via, || {
write_initiative(level);
Ok(format!(
"{}: {}",
Field::Initiative.label(),
Field::Initiative.current()
))
})
.unwrap_or_else(|never| (never, None))
}
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_personality(
current: &mut newt_core::role_profile::PersonalityTraits,
next: newt_core::role_profile::PersonalityTraits,
via: &str,
) -> String {
journalled_personality(current, next, via).0
}
#[cfg(feature = "rich-tui")]
fn journalled_personality(
current: &mut newt_core::role_profile::PersonalityTraits,
next: newt_core::role_profile::PersonalityTraits,
via: &str,
) -> (String, Option<SettingChange>) {
recorded_values("psyche", (*current).into(), via, || {
*current = next;
let to = newt_core::settings_receipt::SettingValue::from(*current);
Ok((format!("personality overrides: {to}"), to))
})
.unwrap_or_else(|never| (never, None))
}
fn write_cognition(choice: newt_core::cognition::CognitionOverride) {
newt_core::cognition::set_cli_cognition(choice);
newt_core::runtime::mark_cognition_choice(choice);
}
fn write_tenacity(level: Option<newt_core::Tenacity>) {
match level {
Some(level) => newt_core::tenacity::set_cli_tenacity(level),
None => newt_core::tenacity::clear_cli_tenacity(),
}
newt_core::runtime::mark_tenacity_choice(level);
}
fn write_initiative(level: Option<newt_core::Initiative>) {
match level {
Some(level) => newt_core::initiative::set_cli_initiative(level),
None => newt_core::initiative::clear_cli_initiative(),
}
newt_core::runtime::mark_initiative_choice(level);
}
fn cognition_token(choice: newt_core::cognition::CognitionOverride) -> String {
use newt_core::cognition::CognitionOverride;
match choice {
CognitionOverride::Unset => "auto".to_string(),
CognitionOverride::Off => "off".to_string(),
CognitionOverride::Set(level) => level.label().to_string(),
}
}
fn tenacity_token(level: Option<newt_core::Tenacity>) -> String {
level.map_or_else(|| "auto".to_string(), |l| l.label().to_string())
}
fn initiative_token(level: Option<newt_core::Initiative>) -> String {
level.map_or_else(|| "auto".to_string(), |l| l.label().to_string())
}
pub(crate) fn moved_notice(verb: &str, field: Field) -> String {
format!(
"(/{verb} still works; this setting now lives in /settings {})",
field.name()
)
}
pub(crate) fn run(ask: Ask<'_>, rest: &str) -> Vec<String> {
let mut parts = rest.trim_start().splitn(2, char::is_whitespace);
let first = parts.next().unwrap_or("").trim();
let raw_second = parts.next().unwrap_or("");
let second = raw_second.trim();
if !first.is_empty() {
let Some(field) = Field::from_token(first) else {
let known: Vec<&str> = Field::ALL.iter().map(|f| f.name()).collect();
return vec![format!(
"/settings has no `{first}` setting (have: {})",
known.join(", ")
)];
};
let is_text = matches!(field.value_space(), ValueSpace::Text { .. });
let value = if is_text { raw_second } else { second };
if !value.trim().is_empty() {
return vec![match apply_and_record(field, value, "/settings") {
Ok(msg) | Err(msg) => msg,
}];
}
return ask_value(ask, field);
}
let menu = field_menu();
let Some(choice) = ask_choice(ask, &menu) else {
return vec!["settings: cancelled".to_string()];
};
let Some(index) = choice.parse::<usize>().ok().filter(|i| *i >= 1) else {
return vec!["settings: cancelled".to_string()];
};
let Some(field) = Field::ALL.get(index - 1).copied() else {
return vec!["settings: cancelled".to_string()];
};
ask_value(ask, field)
}
fn ask_value(ask: Ask<'_>, field: Field) -> Vec<String> {
ask_and_apply(ask, field, "/settings")
}
pub(crate) fn ask_and_apply(ask: Ask<'_>, field: Field, via: &str) -> Vec<String> {
let cancelled = || vec!["settings: cancelled".to_string()];
let definition = value_menu(field);
let value = match field.value_space() {
ValueSpace::Number { .. } => {
let interaction = SurfaceInteraction::blocking(definition.clone());
let HumanQuestionOutcome::Answer(answer) = ask(&interaction) else {
return cancelled();
};
let answer = answer.trim().to_string();
if answer.is_empty() {
return cancelled();
}
answer
}
ValueSpace::Text { .. } => {
let interaction = SurfaceInteraction::blocking(definition.clone());
let HumanQuestionOutcome::Answer(answer) = ask(&interaction) else {
return cancelled();
};
if answer.trim().is_empty() {
return cancelled();
}
answer
}
ValueSpace::Choice(values) => {
let Some(choice) = ask_choice(ask, &definition) else {
return cancelled();
};
let Some(index) = choice.parse::<usize>().ok().filter(|i| *i >= 1) else {
return cancelled();
};
let Some((value, _)) = values.get(index - 1) else {
return cancelled();
};
(*value).to_string()
}
};
vec![match apply_and_record(field, &value, via) {
Ok(msg) | Err(msg) => msg,
}]
}
fn ask_choice(ask: Ask<'_>, definition: &InteractionDefinition) -> Option<String> {
let interaction = SurfaceInteraction::blocking(definition.clone());
match ask(&interaction) {
HumanQuestionOutcome::Answer(answer) => {
newt_core::interaction_form::resolve(definition, answer.trim())
.map(|id| id.as_str().to_string())
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn settings_guard() -> newt_core::test_guard::GlobalSettingsGuard {
newt_core::test_guard::GlobalSettingsGuard::acquire()
}
fn answering(
answers: &'static [&'static str],
) -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
let next = std::cell::Cell::new(0usize);
move |_| {
let i = next.get();
next.set(i + 1);
answers.get(i).map_or(HumanQuestionOutcome::Cancelled, |a| {
HumanQuestionOutcome::Answer((*a).to_string())
})
}
}
fn answering_owned(
answers: Vec<String>,
) -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
let next = std::cell::Cell::new(0usize);
move |_| {
let i = next.get();
next.set(i + 1);
answers.get(i).map_or(HumanQuestionOutcome::Cancelled, |a| {
HumanQuestionOutcome::Answer(a.clone())
})
}
}
fn declining() -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
|_| HumanQuestionOutcome::Cancelled
}
#[test]
fn a_full_deep_link_applies_without_a_question() {
let _g = settings_guard();
let asked = std::cell::Cell::new(false);
let ask = |_: &SurfaceInteraction| {
asked.set(true);
HumanQuestionOutcome::Cancelled
};
let out = run(&ask, "edit-mode emacs");
assert!(!asked.get(), "a fully-specified deep link must not prompt");
assert_eq!(out, vec!["line-editor key bindings: emacs".to_string()]);
assert_eq!(Field::EditMode.current(), "emacs");
}
#[test]
fn the_form_walks_field_then_value() {
let _g = settings_guard();
let out = run(&answering(&["1", "1"]), "");
assert_eq!(out, vec!["line-editor key bindings: vi".to_string()]);
assert_eq!(Field::EditMode.current(), "vi");
}
#[test]
fn cancelling_the_form_changes_no_setting() {
let _g = settings_guard();
let _ = apply(Field::EditMode, "nano");
let before = Field::EditMode.current();
let out = run(&declining(), "");
assert_eq!(out, vec!["settings: cancelled".to_string()]);
assert_eq!(
Field::EditMode.current(),
before,
"a cancelled form must not write"
);
}
#[test]
fn an_unaccepted_value_is_refused_and_writes_nothing() {
let _g = settings_guard();
let _ = apply(Field::EditMode, "vi");
let err = apply(Field::EditMode, "ed").expect_err("`ed` is not an edit mode");
assert!(err.contains("vi, emacs, nano"), "{err}");
assert_eq!(Field::EditMode.current(), "vi", "a refusal must not write");
}
#[test]
fn the_vim_alias_the_old_verb_took_still_resolves() {
assert_eq!(Field::EditMode.accepts("vim").as_deref(), Some("vi"));
assert_eq!(Field::EditMode.accepts("VI").as_deref(), Some("vi"));
assert_eq!(Field::EditMode.accepts("ed"), None);
}
#[test]
fn an_unknown_field_names_the_ones_that_exist() {
let out = run(&declining(), "nosuchsetting");
assert!(out[0].contains("no `nosuchsetting` setting"), "{out:?}");
assert!(out[0].contains("edit-mode"), "{out:?}");
}
#[test]
fn the_menus_are_definitions_carrying_every_choice() {
let _g = settings_guard();
let menu = value_menu(Field::EditMode);
let rendered = newt_core::markup::plain::render(&menu);
for value in ["vi", "emacs", "nano"] {
assert!(rendered.contains(value), "{value} missing: {rendered}");
}
assert!(
rendered.contains("(current)"),
"the current value is marked: {rendered}"
);
assert_eq!(field_menu().controls.len(), 1, "one choice control");
}
#[test]
fn each_field_reports_what_its_own_resolver_says() {
let _g = settings_guard();
for (field, value) in [
(Field::EditMode, "nano"),
(Field::Tenacity, "relentless"),
(Field::Initiative, "decisive"),
(Field::Cognition, "thoughtful"),
(Field::Thinking, "off"),
(Field::Nudge, "off"),
] {
apply(field, value).expect("an offered value applies");
assert_eq!(field.current(), value, "{} did not read back", field.name());
}
for (field, value) in [
(Field::Tenacity, "auto"),
(Field::Initiative, "auto"),
(Field::Cognition, "auto"),
(Field::Thinking, "fold"),
(Field::Nudge, "on"),
] {
apply(field, value).expect("an offered value applies");
assert_eq!(field.current(), value, "{} did not release", field.name());
}
}
#[test]
fn the_dial_levels_are_the_dials_own() {
let offered = Field::Tenacity.offered();
for level in newt_core::Tenacity::all() {
assert!(offered.contains(&level.label()), "{level:?} not offered");
}
assert!(offered.contains(&"auto"), "the release value is missing");
assert_eq!(offered.len(), newt_core::Tenacity::all().len() + 1);
let offered = Field::Initiative.offered();
for level in newt_core::Initiative::all() {
assert!(offered.contains(&level.label()), "{level:?} not offered");
}
assert_eq!(offered.len(), newt_core::Initiative::all().len() + 1);
let offered = Field::Cognition.offered();
for level in newt_core::role_profile::Cognition::all() {
assert!(offered.contains(&level.label()), "{level:?} not offered");
}
assert!(offered.contains(&"off") && offered.contains(&"auto"));
}
#[test]
fn the_detail_field_shares_one_knob_with_spill_and_the_toggle() {
let _guard = settings_guard();
newt_core::config::set_session_spill_lines(None);
assert_eq!(Field::Detail.current(), "auto", "unpinned follows config");
assert_eq!(
apply(Field::Detail, "12"),
Ok("tool-output detail rows: 12".to_string())
);
assert_eq!(newt_core::config::session_spill_lines(), Some(12));
let toggled = crate::toggle_spill_detail(newt_core::config::session_spill_lines(), 3);
newt_core::config::set_session_spill_lines(toggled);
assert_eq!(Field::Detail.current(), "0", "0 is this knob's unbounded");
assert_eq!(newt_core::config::session_spill_lines(), Some(0));
}
#[test]
fn releasing_detail_is_not_the_same_as_setting_it_unbounded() {
let _guard = settings_guard();
apply(Field::Detail, "0").expect("0 is in range");
assert_eq!(newt_core::config::session_spill_lines(), Some(0));
assert_eq!(Field::Detail.current(), "0");
apply(Field::Detail, "auto").expect("auto releases");
assert_eq!(
newt_core::config::session_spill_lines(),
None,
"release must REMOVE the override, not store a word"
);
assert_eq!(Field::Detail.current(), "auto");
}
#[test]
fn the_detail_verbs_vocabulary_survives_absorption() {
for word in ["reset", "default", "config"] {
assert_eq!(
Field::Detail.accepts(word),
Some("auto".to_string()),
"`/spill {word}` released the override"
);
}
for word in ["full", "unbounded", "all"] {
assert_eq!(Field::Detail.accepts(word), Some("0".to_string()), "{word}");
}
assert_eq!(Field::Detail.accepts("40"), Some("40".to_string()));
assert_eq!(Field::Detail.accepts("-1"), None);
assert_eq!(Field::Detail.accepts("banana"), None);
}
#[test]
fn the_posture_field_reports_and_releases_what_is_installed() {
let _guard = settings_guard();
newt_core::posture::set_active_posture(None);
assert_eq!(Field::Posture.current(), "off");
newt_core::posture::set_active_posture(Some(newt_core::posture::ActivePosture {
name: "locked".to_string(),
preset_name: "strict".to_string(),
clamp: newt_core::Caveats::default(),
clamp_summary: "deny writes".to_string(),
skill_body: None,
framing: None,
}));
assert_eq!(Field::Posture.current(), "locked");
assert_eq!(
apply(Field::Posture, "off"),
Ok("permission posture: off".to_string())
);
assert!(
newt_core::posture::active_posture().is_none(),
"`off` releases the clamp rather than storing the word"
);
}
async fn run_isolated_child(
test: &str,
cwd: &std::path::Path,
home: &std::path::Path,
envs: &[(&str, &std::ffi::OsStr)],
) -> std::process::Output {
let mut command = tokio::process::Command::new(std::env::current_exe().unwrap());
for (key, _) in std::env::vars_os() {
if key.to_string_lossy().starts_with("NEWT_") {
command.env_remove(key);
}
}
for key in [
"OPENAI_BASE_URL",
"OPENAI_API_KEY",
"OPENAI_MODEL",
"OLLAMA_HOST",
] {
command.env_remove(key);
}
command
.args(["--exact", test, "--nocapture"])
.current_dir(cwd)
.env("HOME", home)
.env("USERPROFILE", home)
.envs(envs.iter().copied())
.stdin(std::process::Stdio::null())
.kill_on_drop(true);
tokio::time::timeout(newt_core::test_guard::HANG_GUARD, command.output())
.await
.expect("settings fixture watchdog expired; child is killed on drop")
.unwrap()
}
#[tokio::test]
async fn posture_settings_resolution_failure_is_not_a_success_receipt() {
const CHILD: &str = "NEWT_POSTURE_CONFIG_FIXTURE";
if std::env::var_os(CHILD).is_none() {
let fixture = tempfile::tempdir().unwrap();
let config_dir = fixture.path().join(".newt");
std::fs::create_dir_all(&config_dir).unwrap();
let config = config_dir.join("config.toml");
std::fs::write(&config, "[modes.broken]\npreset = 'missing'\n").unwrap();
let output = run_isolated_child(
"settings_form::tests::posture_settings_resolution_failure_is_not_a_success_receipt",
fixture.path(),
fixture.path(),
&[
("NEWT_CONFIG_DIR", config_dir.as_os_str()),
("NEWT_CONFIG", config.as_os_str()),
(CHILD, "1".as_ref()),
],
)
.await;
assert!(
output.status.success(),
"isolated settings fixture failed: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(String::from_utf8_lossy(&output.stdout).contains("1 passed"));
return;
}
let _guard = settings_guard();
let receipts = std::env::current_dir().unwrap().join("receipts.jsonl");
let saved_posture = newt_core::posture::active_posture();
std::env::set_var(newt_core::settings_receipt::RECEIPT_PATH_ENV, &receipts);
let previous = newt_core::posture::ActivePosture {
name: "existing".into(),
preset_name: "strict".into(),
clamp: newt_core::Caveats::default(),
clamp_summary: "deny writes".into(),
skill_body: None,
framing: Some("Keep the existing guidance.".into()),
};
newt_core::posture::set_active_posture(Some(previous.clone()));
let result = apply_and_record(Field::Posture, "broken", "/settings posture");
let installed = newt_core::posture::active_posture();
newt_core::posture::set_active_posture(saved_posture);
let error = result.expect_err("an unresolved preset must fail");
assert!(
error.contains("names preset 'missing'")
&& error.contains("[permission_presets.missing]"),
"must reach the missing-preset resolver, not an unrelated config error: {error}"
);
let installed = installed.expect("failed resolution must preserve the old posture");
assert_eq!(installed.name, previous.name);
assert_eq!(installed.clamp, previous.clamp);
assert_eq!(installed.framing, previous.framing);
assert!(
!receipts.exists(),
"failed settings must not emit success receipts"
);
}
#[test]
fn the_posture_vocabulary_always_offers_the_release() {
let offered = Field::Posture.offered();
assert!(
offered.contains(&"off"),
"releasing the clamp is always available: {offered:?}"
);
assert_eq!(offered.first().copied(), Some("off"), "and it leads");
}
#[test]
fn the_compaction_field_and_its_verb_resolve_to_one_state() {
let _guard = settings_guard();
newt_core::process_env::remove_var("NEWT_COMPACTION_TRIGGER");
assert_eq!(
Field::Compaction.current(),
newt_core::CompactionTriggerPolicy::default().keyword(),
"unpinned, the field reports what the resolver resolves"
);
assert_eq!(
apply(Field::Compaction, "message_count"),
Ok("automatic-compaction trigger: message_count".to_string())
);
assert_eq!(Field::Compaction.current(), "message_count");
assert!(newt_core::config::compaction_trigger_is_session_pinned());
}
#[test]
fn the_compaction_fields_choices_are_the_policies_that_exist() {
use newt_core::CompactionTriggerPolicy as P;
let offered = Field::Compaction.offered();
assert_eq!(
offered,
vec![P::HeadroomAware.keyword(), P::MessageCount.keyword()]
);
assert_eq!(Field::Compaction.accepts("aggressive"), None);
assert_eq!(
Field::Compaction.accepts("HEADROOM_AWARE"),
Some("headroom_aware".to_string())
);
}
#[test]
fn a_quoted_template_reaches_the_field_verbatim() {
let _guard = settings_guard();
newt_core::process_env::remove_var("NEWT_PROMPT");
let out = run(&declining(), "prompt \"[$TIME] ❯ \"");
assert_eq!(out.len(), 1, "{out:?}");
assert_eq!(
Field::Prompt.current(),
"[$TIME] ❯ ",
"the trailing space is the whole point of quoting it"
);
run(&declining(), "prompt \"a b\"");
assert_eq!(Field::Prompt.current(), "a b");
}
#[test]
fn resetting_the_prompt_releases_it_to_config() {
let _guard = settings_guard();
newt_core::process_env::set_var("NEWT_PROMPT", "custom ");
assert_eq!(Field::Prompt.current(), "custom ");
run(&declining(), "prompt reset");
assert!(
std::env::var("NEWT_PROMPT").is_err(),
"reset must remove the override, not store the word"
);
assert_ne!(Field::Prompt.current(), "reset");
}
#[test]
fn the_text_field_offers_a_release_and_a_shape_not_a_vocabulary() {
assert_eq!(Field::Prompt.offered(), vec!["reset"]);
let hint = Field::Prompt.accepts_hint();
assert!(hint.contains("reset"), "{hint}");
assert!(hint.contains("template"), "{hint}");
}
#[test]
fn the_mode_fields_choices_are_the_enums_own() {
use newt_core::operating_mode::OperatingMode;
let offered = Field::Mode.offered();
let want: Vec<&str> = OperatingMode::all().iter().map(|m| m.as_str()).collect();
assert_eq!(offered, want, "the form drifted from the mode vocabulary");
assert!(!want.is_empty(), "an empty list would make this vacuous");
assert!(offered.contains(&"full-auto"));
}
#[test]
fn the_mode_field_and_its_verb_resolve_to_one_state() {
use newt_core::operating_mode::{session_operating_mode, OperatingMode};
let _guard = settings_guard();
newt_core::process_env::remove_var("NEWT_MODE");
assert_eq!(session_operating_mode(), OperatingMode::Chat);
assert_eq!(Field::Mode.current(), "chat");
assert_eq!(
apply(Field::Mode, "plan"),
Ok("operating mode: plan".to_string())
);
assert_eq!(session_operating_mode(), OperatingMode::Plan);
assert_eq!(Field::Mode.current(), "plan");
}
#[test]
fn the_mode_verbs_aliases_survive_absorption() {
assert_eq!(Field::Mode.accepts("developer"), Some("dev".to_string()));
assert_eq!(Field::Mode.accepts("sysadmin"), Some("admin".to_string()));
assert_eq!(
Field::Mode.accepts("FULL-AUTO"),
Some("full-auto".to_string())
);
assert_eq!(Field::Mode.accepts("nonsense"), None);
}
#[test]
fn the_prefix_field_persists_and_takes_effect_now() {
let _guard = settings_guard();
newt_core::process_env::remove_var("NEWT_PREFIX_KEY");
let path = newt_core::Config::user_config_path().expect("a config path");
assert_eq!(
apply(Field::Prefix, "ctrl+a"),
Ok("meta prefix key: ctrl+a".to_string())
);
let saved = config_fs::read(&path);
assert!(
saved.contains("[tui]") && saved.contains("prefix_key = \"ctrl+a\""),
"{saved}"
);
assert_eq!(Field::Prefix.current(), "ctrl+a");
assert_eq!(prefix_setting(), "ctrl+a");
assert!(
apply(Field::Prefix, "ctrl+q").is_err(),
"not an offered chord"
);
assert_eq!(
config_fs::read(&path),
saved,
"a refused value writes nothing"
);
newt_core::process_env::remove_var("NEWT_PREFIX_KEY");
}
#[test]
fn the_tools_fields_persist_the_exposure_policy_and_refuse_unknown_names() {
let _guard = settings_guard();
let saved_policy = || {
let path = newt_core::Config::user_config_path().expect("a config path");
toml::from_str::<newt_core::Config>(&config_fs::read(&path))
.expect("valid TOML")
.tool_exposure()
};
assert_eq!(
apply(Field::Tools, "pi"),
Ok("tools the model sees: pi".to_string())
);
assert!(saved_policy().is_pi_preset());
assert_eq!(tools_token(&saved_policy()), "pi");
assert_eq!(
apply(Field::Tools, "read_file, run_command,find"),
Ok("tools the model sees: read_file, run_command,find".to_string())
);
assert_eq!(tools_token(&saved_policy()), "read_file,run_command,find");
let path = newt_core::Config::user_config_path().expect("a config path");
let before = config_fs::read(&path);
let refused = apply(Field::Tools, "read_file,frobnicate").unwrap_err();
assert!(refused.contains("frobnicate"), "{refused}");
assert_eq!(
config_fs::read(&newt_core::Config::user_config_path().unwrap()),
before,
"a refused value writes nothing"
);
apply(Field::Tools, "read_file,github__search").expect("mcp names pass");
assert_eq!(
apply(Field::ReadOnly, "on"),
Ok("hide the write tools: on".to_string())
);
assert!(saved_policy().read_only);
assert_eq!(
saved_policy().status_label().as_deref(),
Some("2 tools · read-only")
);
assert_eq!(
apply(Field::ReadOnly, "off"),
Ok("hide the write tools: off".to_string())
);
assert!(!saved_policy().read_only);
assert_eq!(
apply(Field::Tools, "all"),
Ok("tools the model sees: all".to_string())
);
assert_eq!(saved_policy().allow_list, None);
assert_eq!(tools_token(&saved_policy()), "all");
assert_eq!(saved_policy().status_label(), None);
}
#[test]
fn an_empty_selection_stays_no_tools_end_to_end() {
let _guard = settings_guard();
let saved = || {
let path = newt_core::Config::user_config_path().expect("a config path");
toml::from_str::<newt_core::Config>(&config_fs::read(&path))
.expect("valid TOML")
.tool_exposure()
};
apply(Field::Tools, "read_file").unwrap();
let panel = crate::settings_form::tools_token(&newt_core::ToolExposureConfig {
allow_list: Some(Vec::new()),
..Default::default()
});
assert_eq!(panel, "none");
assert_eq!(Field::Tools.accepts(&panel).as_deref(), Some("none"));
apply(Field::Tools, &panel).unwrap();
assert_eq!(saved().allow_list, Some(Vec::new()));
assert_eq!(tools_token(&saved()), "none");
assert!(
apply(Field::Tools, ",").is_err(),
"a bare separator is not a list"
);
apply(Field::Tools, "all").unwrap();
assert_eq!(saved().allow_list, None);
}
#[test]
fn a_shadowed_exposure_write_is_refused_naming_the_layer() {
let _guard = settings_guard();
SHADOWED_EXPOSURE.with(|s| {
*s.borrow_mut() = Some(newt_core::ToolExposureConfig {
read_only: false,
allow_list: Some(vec!["write_file".into()]),
..Default::default()
});
});
let ro = apply(Field::ReadOnly, "on").unwrap_err();
let list = apply(Field::Tools, "pi").unwrap_err();
for refused in [&ro, &list] {
assert!(refused.contains("NOT in force"), "{refused}");
assert!(refused.contains("overrides"), "{refused}");
}
assert!(apply(Field::ReadOnly, "off").is_ok());
assert!(apply(Field::Tools, "write_file").is_ok());
SHADOWED_EXPOSURE.with(|s| *s.borrow_mut() = None);
}
#[test]
fn a_shadowed_write_leaves_no_receipt() {
let _guard = settings_guard();
SHADOWED_EXPOSURE
.with(|s| *s.borrow_mut() = Some(newt_core::ToolExposureConfig::default()));
let result = apply_and_record(Field::ReadOnly, "on", "/settings read-only");
SHADOWED_EXPOSURE.with(|s| *s.borrow_mut() = None);
assert!(result.unwrap_err().contains("NOT in force"));
}
#[test]
fn empty_tools_input_is_refused_not_released() {
let _guard = settings_guard();
apply(Field::Tools, "read_file").unwrap();
let path = newt_core::Config::user_config_path().expect("a config path");
let before = config_fs::read(&path);
for typed in ["", " ", "\"\"", "\" \""] {
let value = Field::Tools.accepts(typed).expect("reaches the parser");
let refused = apply(Field::Tools, &value).unwrap_err();
assert!(
refused.contains("`none`") && refused.contains("`all`"),
"{typed:?}: {refused}"
);
}
assert_eq!(
config_fs::read(&path),
before,
"a refused value writes nothing"
);
assert_eq!(Field::Tools.accepts("all").as_deref(), Some("all"));
}
#[test]
fn the_overriding_layer_is_the_one_that_sets_the_key() {
let layer =
|label: &str, toml: &str| (label.to_string(), toml.to_string(), label == "pinned");
let wants_ro = |c: &newt_core::ToolExposureConfig| c.read_only;
let project = layer("project", "[tool_exposure]\nread_only = false\n");
let pinned_ok = layer("pinned", "[tool_exposure]\nread_only = true\n");
let pinned_bad = layer("pinned", "[tool_exposure]\nread_only = false\n");
let silent = layer("project", "[tool_exposure]\nallow_list = []\n");
let blame =
|layers: &[(String, String, bool)]| overriding_layer(layers, "read_only", &wants_ro);
assert_eq!(
blame(&[project.clone(), pinned_ok.clone()]).as_deref(),
Some("project")
);
assert_eq!(
blame(std::slice::from_ref(&pinned_bad)).as_deref(),
Some("pinned")
);
assert_eq!(
blame(&[silent, pinned_bad]).as_deref(),
Some("pinned"),
"a layer that does not set the key is not blamed"
);
assert_eq!(blame(std::slice::from_ref(&pinned_ok)), None);
assert_eq!(blame(&[]), None);
for omitted in ["", "[tui]\nx = 1\n", "[tool_exposure]\nallow_list = []\n"] {
assert_eq!(
blame(&[layer("pinned", omitted)]).as_deref(),
Some("pinned"),
"{omitted:?}"
);
}
assert_eq!(blame(&[layer("project", "")]), None);
}
#[tokio::test]
async fn layered_config_names_the_winning_layer_through_the_real_resolver() {
const CHILD: &str = "NEWT_LAYER_CASE";
if let Ok(case) = std::env::var(CHILD) {
let _guard = settings_guard();
let receipts = std::env::current_dir().unwrap().join("receipts.jsonl");
std::env::set_var(newt_core::settings_receipt::RECEIPT_PATH_ENV, &receipts);
for (field, value, verb) in [
(Field::ReadOnly, "on", "/settings read-only"),
(Field::Tools, "none", "/settings tools"),
] {
let outcome = apply_and_record(field, value, verb);
let wrote_receipt = receipts.exists();
match case.as_str() {
"user" => {
outcome.expect("nothing shadows the user file");
assert!(wrote_receipt, "a took write is receipted");
}
other => {
let error = outcome.unwrap_err();
assert!(error.contains("NOT in force"), "{other}: {error}");
let blamed = if other == "pinned" {
"pinned by NEWT_CONFIG"
} else {
"project config"
};
assert!(error.contains(blamed), "{other} {verb}: {error}");
assert!(!wrote_receipt, "a refused write is not receipted");
}
}
}
return;
}
let off = "[tool_exposure]\nread_only = false\nallow_list = [\"write_file\"]\n";
let on = "[tool_exposure]\nread_only = true\nallow_list = []\n";
for (case, pinned, project) in [
("user", None, None),
("project", None, Some(off)),
("pinned", Some(off), None),
("pinned+project", Some(on), Some(off)),
(
"pinned",
Some("[tool_exposure]\nallow_list = [\"read_file\"]\n"),
None,
),
("pinned", Some("[tool_exposure]\nread_only = false\n"), None),
("pinned", Some("[tui]\nverbose = true\n"), None),
] {
let fx = tempfile::tempdir().unwrap();
let home = fx.path().join("home");
let cfg = home.join(".newt");
let work = fx.path().join("work");
std::fs::create_dir_all(&cfg).unwrap();
std::fs::create_dir_all(work.join(".newt")).unwrap();
let pinned_path = fx.path().join("pinned.toml");
let project_path = work.join(".newt/config.toml");
if let Some(text) = pinned {
std::fs::write(&pinned_path, text).unwrap();
}
if let Some(text) = project {
std::fs::write(&project_path, text).unwrap();
}
let case_env = if case == "pinned+project" {
"project"
} else {
case
};
let mut envs: Vec<(&str, &std::ffi::OsStr)> = vec![
("NEWT_CONFIG_DIR", cfg.as_os_str()),
(config_fs::REAL_LAYERS, "1".as_ref()),
(CHILD, case_env.as_ref()),
];
if pinned.is_some() {
envs.push(("NEWT_CONFIG", pinned_path.as_os_str()));
}
let output = run_isolated_child(
"settings_form::tests::layered_config_names_the_winning_layer_through_the_real_resolver",
&work,
&home,
&envs,
)
.await;
assert!(
output.status.success()
&& String::from_utf8_lossy(&output.stdout).contains("1 passed"),
"{case}: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
if let Some(text) = pinned {
assert_eq!(
std::fs::read_to_string(&pinned_path).unwrap(),
text,
"{case}: pinned untouched"
);
}
if let Some(text) = project {
assert_eq!(
std::fs::read_to_string(&project_path).unwrap(),
text,
"{case}: project untouched"
);
}
}
}
#[test]
fn the_markdown_field_and_its_verb_resolve_to_one_state() {
let _guard = settings_guard();
newt_core::process_env::remove_var("NEWT_MARKDOWN");
assert_eq!(
Field::Markdown.current(),
crate::migration_notices::read(newt_core::config::session_markdown_mode).keyword(),
"unpinned, the field reports what the resolver resolves"
);
assert_eq!(
apply(Field::Markdown, "off"),
Ok("markdown rendering: off".to_string())
);
assert_eq!(Field::Markdown.current(), "off");
assert!(newt_core::config::markdown_is_session_pinned());
assert_eq!(
apply(Field::Markdown, "auto"),
Ok("markdown rendering: auto".to_string())
);
assert_eq!(Field::Markdown.current(), "auto");
assert!(
newt_core::config::markdown_is_session_pinned(),
"`auto` is still an operator pin — it beats a config that says off"
);
}
#[test]
fn the_markdown_verbs_aliases_survive_absorption() {
assert_eq!(Field::Markdown.accepts("always"), Some("on".to_string()));
assert_eq!(Field::Markdown.accepts("never"), Some("off".to_string()));
assert_eq!(Field::Markdown.accepts("AUTO"), Some("auto".to_string()));
assert_eq!(Field::Markdown.accepts("sometimes"), None);
}
#[test]
fn the_aliases_the_absorbed_verbs_took_still_resolve() {
for (field, typed, want) in [
(Field::EditMode, "vim", "vi"),
(Field::Tenacity, "inherit", "auto"),
(Field::Tenacity, "reset", "auto"),
(Field::Initiative, "inherit", "auto"),
(Field::Initiative, "reset", "auto"),
(Field::Cognition, "none", "off"),
(Field::Cognition, "reset", "auto"),
(Field::Cognition, "persona", "auto"),
] {
assert_eq!(
field.accepts(typed).as_deref(),
Some(want),
"/{} {typed} stopped resolving",
field.name()
);
}
assert_eq!(Field::Tenacity.accepts("obsessive"), None);
assert_eq!(Field::Initiative.accepts("relentless"), None);
assert_eq!(Field::Cognition.accepts("hard"), None);
assert_eq!(Field::Thinking.accepts("maybe"), None);
}
#[test]
fn a_refused_dial_value_changes_nothing() {
let _g = settings_guard();
apply(Field::Tenacity, "relentless").expect("a level applies");
let err = apply(Field::Tenacity, "obsessive").expect_err("not a level");
assert!(err.contains("relentless"), "{err}");
assert_eq!(Field::Tenacity.current(), "relentless", "a refusal wrote");
assert!(apply(Field::Tenacity, "insistent").is_err());
assert_eq!(Field::Tenacity.current(), "relentless", "a refusal wrote");
}
#[test]
fn every_field_declares_where_its_receipt_lands() {
use crate::slash_registry::{receipt_for, Receipt};
for field in Field::ALL {
assert_eq!(
receipt_for(field.name()),
Receipt::Journal,
"/settings {} has no declared receipt destination",
field.name()
);
assert!(change_for(field.name(), "a".into(), "b".into(), "/settings").is_some());
}
assert_eq!(receipt_for("remember"), Receipt::Event);
assert_eq!(receipt_for("setup"), Receipt::Missing);
assert_eq!(receipt_for("help"), Receipt::None_);
assert_eq!(receipt_for("zzznotacommand"), Receipt::Missing);
}
#[test]
fn the_recorded_change_names_the_route_and_the_transition() {
let change = change_for(Field::EditMode.name(), "vi".into(), "emacs".into(), "/vi")
.expect("declared");
assert_eq!(change.setting, "edit-mode");
assert_eq!(change.from.to_string(), "vi");
assert_eq!(change.to.to_string(), "emacs");
assert_eq!(change.via, "/vi");
assert_eq!(
change.schema,
newt_core::settings_receipt::SETTING_CHANGE_SCHEMA_V1
);
}
#[test]
fn the_round_cap_accepts_a_bounded_number_and_a_release_token() {
let max = newt_core::tenacity::RELENTLESS_TOOL_ROUND_TARGET;
assert_eq!(Field::Rounds.accepts("50").as_deref(), Some("50"));
assert_eq!(Field::Rounds.accepts(" 0050 ").as_deref(), Some("50"));
assert_eq!(
Field::Rounds.accepts(&max.to_string()).as_deref(),
Some(max.to_string().as_str())
);
for release in ["auto", "reset", "default"] {
assert_eq!(Field::Rounds.accepts(release).as_deref(), Some("auto"));
}
assert_eq!(Field::Rounds.accepts("0"), None, "0 rounds is not a limit");
assert_eq!(Field::Rounds.accepts(&(max + 1).to_string()), None);
assert_eq!(
Field::Rounds.accepts("unlimited"),
None,
"a verb affordance, not a value"
);
assert_eq!(Field::Rounds.accepts("banana"), None);
}
#[test]
fn the_number_field_is_a_text_definition_not_a_menu() {
let _g = settings_guard();
let asked = value_menu(Field::Rounds);
assert!(
asked
.controls
.iter()
.all(|c| !matches!(c.kind, newt_interaction::ControlKind::Choice { .. })),
"a number has no menu to pick from"
);
let rendered = newt_core::markup::plain::render(&asked);
assert!(rendered.contains("10000"), "the bound is shown: {rendered}");
assert!(value_menu(Field::Tenacity)
.controls
.iter()
.any(|c| matches!(c.kind, newt_interaction::ControlKind::Choice { .. })));
}
#[test]
fn the_form_walks_to_the_round_cap_and_takes_a_typed_number() {
let _g = settings_guard();
newt_core::tenacity::set_session_tool_rounds(None);
let index = Field::ALL
.iter()
.position(|f| *f == Field::Rounds)
.expect("rounds is offered")
+ 1;
let out = run(
&answering_owned(vec![index.to_string(), "320".to_string()]),
"",
);
assert_eq!(out, vec!["tool-call round limit: 320".to_string()]);
assert_eq!(newt_core::tenacity::session_tool_rounds(), Some(320));
let out = run(
&answering_owned(vec![index.to_string(), " ".to_string()]),
"",
);
assert_eq!(out, vec!["settings: cancelled".to_string()]);
assert_eq!(
newt_core::tenacity::session_tool_rounds(),
Some(320),
"backing out of the number must not clear the cap"
);
}
#[test]
fn the_round_cap_records_its_derivation_not_just_the_number() {
use newt_core::settings_receipt::SettingValue;
let _g = settings_guard();
newt_core::tenacity::set_configured_tool_rounds(Some(40));
newt_core::tenacity::set_cli_tenacity(newt_core::Tenacity::Relentless);
newt_core::tenacity::set_session_tool_rounds(None);
let from = Field::Rounds.value_now();
apply(Field::Rounds, "320").expect("in range");
let to = Field::Rounds.value_now();
let change = change_for(Field::Rounds.name(), from, to, "/max-rounds").expect("declared");
let SettingValue::ToolRounds(after) = &change.to else {
panic!("the cap was recorded as a bare token: {:?}", change.to);
};
assert_eq!(after.rounds, 320);
assert_eq!(after.configured, 40, "the baseline it escalated over");
assert!(after.is_escalated());
assert_eq!(change.via, "/max-rounds", "the alias typed is bound in");
let token = change_for(
Field::EditMode.name(),
Field::EditMode.value_now(),
Field::EditMode.value_now(),
"/vi",
)
.expect("declared");
assert!(matches!(token.to, SettingValue::Token(_)));
}
#[test]
fn a_cap_with_no_installed_baseline_records_a_token_not_a_guess() {
use newt_core::settings_receipt::SettingValue;
let _g = settings_guard();
newt_core::tenacity::set_configured_tool_rounds(None);
newt_core::tenacity::set_session_tool_rounds(Some(320));
assert!(
matches!(Field::Rounds.value_now(), SettingValue::Token(ref t) if t == "320"),
"with no baseline the cap must record as a token, not a guessed derivation"
);
newt_core::tenacity::set_configured_tool_rounds(Some(40));
assert!(matches!(
Field::Rounds.value_now(),
SettingValue::ToolRounds(_)
));
}
#[test]
fn recording_does_not_change_what_gets_applied() {
let _g = settings_guard();
let recorded = apply_and_record(Field::Tenacity, "relentless", "/settings")
.expect("an offered value applies");
assert_eq!(Field::Tenacity.current(), "relentless");
apply(Field::Tenacity, "auto").expect("released");
let bare = apply(Field::Tenacity, "relentless").expect("an offered value applies");
assert_eq!(recorded, bare, "the recorder changed the outcome");
}
#[cfg(feature = "rich-tui")]
#[test]
fn every_dial_position_is_a_token_the_form_offers() {
use newt_core::cognition::CognitionOverride;
use newt_core::role_profile::Cognition;
use newt_core::Tenacity;
let mut cognition = vec![CognitionOverride::Unset, CognitionOverride::Off];
cognition.extend(Cognition::all().iter().copied().map(CognitionOverride::Set));
for choice in cognition {
let token = cognition_token(choice);
assert!(
Field::Cognition.accepts(&token).is_some(),
"cognition dial position {choice:?} renders {token:?}, which the form refuses"
);
}
let mut tenacity: Vec<Option<Tenacity>> = vec![None];
tenacity.extend(Tenacity::all().iter().copied().map(Some));
for level in tenacity {
let token = tenacity_token(level);
assert!(
Field::Tenacity.accepts(&token).is_some(),
"tenacity dial position {level:?} renders {token:?}, which the form refuses"
);
}
let mut initiative: Vec<Option<newt_core::Initiative>> = vec![None];
initiative.extend(newt_core::Initiative::all().iter().copied().map(Some));
for level in initiative {
let token = initiative_token(level);
assert!(
Field::Initiative.accepts(&token).is_some(),
"initiative dial position {level:?} renders {token:?}, which the form refuses"
);
}
}
#[cfg(feature = "rich-tui")]
#[test]
fn each_typed_door_journals_its_own_setting_and_route() {
use newt_core::cognition::CognitionOverride;
use newt_core::role_profile::Cognition;
let _g = settings_guard();
apply(Field::Cognition, "off").expect("arrange");
let (_, change) =
journalled_cognition(CognitionOverride::Set(Cognition::all()[0]), "/psyche edit");
let change = change.expect("cognition declares a journal destination");
assert_eq!(change.setting, "cognition", "the door journals ITS setting");
assert_eq!(change.via, "/psyche edit", "the route is the one taken");
assert_eq!(change.from.to_string(), "off");
assert_eq!(change.to.to_string(), Cognition::all()[0].label());
apply(Field::Tenacity, "normal").expect("arrange");
let (_, change) = journalled_tenacity(None, "/psyche");
let change = change.expect("tenacity declares a journal destination");
assert_eq!(change.setting, "tenacity");
assert_eq!(change.via, "/psyche");
assert_eq!(change.from.to_string(), "normal");
assert_eq!(change.to.to_string(), "auto", "None releases the override");
apply(Field::Initiative, "patient").expect("arrange");
let (_, change) = journalled_initiative(Some(newt_core::Initiative::Eager), "/psyche");
let change = change.expect("initiative declares a journal destination");
assert_eq!(
change.setting, "initiative",
"the door journals ITS setting"
);
assert_eq!(change.via, "/psyche");
assert_eq!(change.from.to_string(), "patient");
assert_eq!(change.to.to_string(), "eager");
}
#[cfg(feature = "rich-tui")]
#[test]
fn the_typed_door_and_the_token_door_agree() {
use newt_core::cognition::CognitionOverride;
use newt_core::role_profile::Cognition;
use newt_core::Tenacity;
let _g = settings_guard();
let mut cognition = vec![
(CognitionOverride::Unset, "auto".to_string()),
(CognitionOverride::Off, "off".to_string()),
];
cognition.extend(
Cognition::all()
.iter()
.map(|c| (CognitionOverride::Set(*c), c.label().to_string())),
);
for (choice, token) in cognition {
let token = token.as_str();
apply(Field::Cognition, "off").expect("arrange");
let typed = apply_cognition(choice, "/psyche");
let after_typed = Field::Cognition.current();
apply(Field::Cognition, "off").expect("arrange");
let by_token =
apply_and_record(Field::Cognition, token, "/settings").expect("offered token");
assert_eq!(
Field::Cognition.current(),
after_typed,
"same runtime state"
);
assert_eq!(typed, by_token, "same reported change");
}
let mut tenacity = vec![(None, "auto".to_string())];
tenacity.extend(
Tenacity::all()
.iter()
.map(|t| (Some(*t), t.label().to_string())),
);
for (level, token) in tenacity {
let token = token.as_str();
apply(Field::Tenacity, "normal").expect("arrange");
let typed = apply_tenacity(level, "/psyche");
let after_typed = Field::Tenacity.current();
apply(Field::Tenacity, "normal").expect("arrange");
let by_token =
apply_and_record(Field::Tenacity, token, "/settings").expect("offered token");
assert_eq!(Field::Tenacity.current(), after_typed, "same runtime state");
assert_eq!(typed, by_token, "same reported change");
}
let mut initiative = vec![(None, "auto".to_string())];
initiative.extend(
newt_core::Initiative::all()
.iter()
.map(|i| (Some(*i), i.label().to_string())),
);
for (level, token) in initiative {
let token = token.as_str();
apply(Field::Initiative, "measured").expect("arrange");
let typed = apply_initiative(level, "/psyche");
let after_typed = Field::Initiative.current();
apply(Field::Initiative, "measured").expect("arrange");
let by_token =
apply_and_record(Field::Initiative, token, "/settings").expect("offered token");
assert_eq!(
Field::Initiative.current(),
after_typed,
"same runtime state"
);
assert_eq!(typed, by_token, "same reported change");
}
}
#[test]
fn a_deprecated_verb_names_its_replacement_without_retiring_itself() {
let notice = moved_notice("vi", Field::EditMode);
assert!(notice.contains("/vi still works"), "{notice}");
assert!(notice.contains("/settings edit-mode"), "{notice}");
}
#[cfg(feature = "rich-tui")]
#[test]
fn personality_apply_records_typed_override_and_inherit_without_pinning_other_dials() {
use newt_core::role_profile::{PersonalityLevel, PersonalityTraits};
use newt_core::settings_receipt::SettingValue;
let _g = newt_core::test_guard::GlobalSettingsGuard::acquire();
let _ = newt_core::runtime::drain_preference_actions();
let before_cognition = newt_core::cognition::cli_cognition();
let before_tenacity = newt_core::tenacity::cli_tenacity();
let before_initiative = newt_core::initiative::cli_initiative();
let mut current = PersonalityTraits::default();
let next = PersonalityTraits {
warmth: Some(PersonalityLevel::try_from(0).unwrap()),
..Default::default()
};
let (_, receipt) = journalled_personality(&mut current, next, "/psyche edit");
assert_eq!(current, next);
let receipt = receipt.expect("psyche declares its journal destination");
assert_eq!(receipt.setting, "psyche");
assert_eq!(receipt.via, "/psyche edit");
assert_eq!(
receipt.from,
SettingValue::from(PersonalityTraits::default())
);
assert_eq!(receipt.to, SettingValue::from(next));
let (_, reset) =
journalled_personality(&mut current, PersonalityTraits::default(), "/psyche");
let reset = reset.unwrap();
assert_eq!(reset.from, SettingValue::from(next));
assert_eq!(reset.to, SettingValue::from(PersonalityTraits::default()));
assert_eq!(current, PersonalityTraits::default());
assert_eq!(newt_core::cognition::cli_cognition(), before_cognition);
assert_eq!(newt_core::tenacity::cli_tenacity(), before_tenacity);
assert_eq!(newt_core::initiative::cli_initiative(), before_initiative);
assert!(newt_core::runtime::drain_preference_actions().is_empty());
}
}