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,
Compaction,
Detail,
Posture,
Rounds,
Prefix,
GitAuthor,
GitConfig,
GitSigning,
}
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::Compaction,
Self::Detail,
Self::Posture,
Self::Rounds,
Self::Prefix,
Self::GitAuthor,
Self::GitConfig,
Self::GitSigning,
];
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::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",
}
}
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::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",
}
}
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::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::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()),
}
}
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 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> {
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 = newt_core::Config::with_tui_key(&text, key, value).map_err(|e| e.to_string())?;
config_fs::write(&path, &updated).map_err(|e| format!("saving {key}: {e}"))
}
#[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) fn read(path: &Path) -> String {
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<()> {
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::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();
}
}
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"
);
}
#[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 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",
"settings_form::tests::posture_settings_resolution_failure_is_not_a_success_receipt",
"--nocapture",
])
.current_dir(fixture.path())
.env("HOME", fixture.path())
.env("USERPROFILE", fixture.path())
.env("NEWT_CONFIG_DIR", &config_dir)
.env("NEWT_CONFIG", &config)
.env(CHILD, "1")
.stdin(std::process::Stdio::null())
.kill_on_drop(true);
let output = tokio::time::timeout(newt_core::test_guard::HANG_GUARD, command.output())
.await
.expect("settings fixture watchdog expired; child is killed on drop")
.unwrap();
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_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());
}
}