docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
1 parent
ff0fb56c67
commit
a02b555e66
1458 files changed
+10729
-21750
No files matched your search
@@ -28,7 +28,7 @@ static DISMISS_TMP_NONCE: AtomicU64 = AtomicU64::new(0);
|
||||
static REMOTE_CAMPAIGN_CACHE: RwLock<Vec<CampaignEntry>> = RwLock::new(Vec::new());
|
||||
|
||||
/// Seed the process-global remote campaign cache. A `None` settings value (e.g.
|
||||
/// a failed fetch) is a no-op so it can't clobber a previously-seeded cache;
|
||||
/// a failed fetch) is a no-op so it can't clobber an already-seeded cache;
|
||||
/// `Some` with zero campaigns legitimately clears it (campaigns withdrawn).
|
||||
pub fn set_remote_campaigns_from_settings(remote: Option<&RemoteSettings>) {
|
||||
let Some(remote) = remote else {
|
||||
@@ -74,8 +74,9 @@ fn dismiss_campaign_ids_at(
|
||||
let _guard = DISMISS_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
||||
let path = campaigns_state_path(home);
|
||||
// Cross-process advisory lock over the read-modify-write: in leader mode
|
||||
// several kigi processes share `$KIGI_SHARE_DIR`; the in-process mutex alone would
|
||||
// let them lose-update the set. Best-effort; a lock failure still proceeds.
|
||||
// several kigi processes share `$KIGI_SHARE_DIR`, and the in-process mutex
|
||||
// alone would allow them to lose-update the set. Best-effort; a lock
|
||||
// failure still proceeds.
|
||||
let lock = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
@@ -327,7 +328,7 @@ fn apply_campaign_fields(
|
||||
pub fn sync_campaign_fields(cfg: &mut crate::agent::config::Config) {
|
||||
let remote = remote_campaigns_from_settings(cfg.remote_settings.as_ref());
|
||||
// Seed the process-global cache from the parse we already did (skip on `None`
|
||||
// so a failed fetch can't clobber a previously-seeded cache).
|
||||
// so a failed fetch can't clobber an already-seeded cache).
|
||||
if cfg.remote_settings.is_some() {
|
||||
set_remote_campaigns(remote.clone());
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ use toml::Value as TomlValue;
|
||||
use toml::map::Map as TomlMap;
|
||||
|
||||
pub use kigi_mcp::oauth_config::{McpOAuthConfig, McpOAuthConfigMap};
|
||||
// MCP server config value types extracted to `kigi-config-types` (config
|
||||
// MCP server config value types live in `kigi-config-types` (config
|
||||
// dependency inversion); re-exported so `crate::util::config::*` paths keep working.
|
||||
pub use kigi_config_types::{McpJsonOAuthBlock, McpServerConfig, McpServerTransportConfig};
|
||||
// Permission-policy value types likewise extracted; re-exported to keep paths stable.
|
||||
// Permission-policy value types likewise live there; re-exported to keep paths stable.
|
||||
pub use kigi_config_types::{
|
||||
PatternMode, PermissionConfig, PermissionRule, RuleAction, ToolFilter,
|
||||
};
|
||||
// Relay-sync + MCP-config value types extracted; re-exported to keep paths stable.
|
||||
// Relay-sync + MCP-config value types live there too; re-exported to keep paths stable.
|
||||
pub use kigi_config_types::McpConfig;
|
||||
// Worktree-pool config value type extracted; re-exported to keep paths stable.
|
||||
// Worktree-pool config value type lives there too; re-exported to keep paths stable.
|
||||
pub use kigi_config_types::PoolConfig;
|
||||
|
||||
/// TUI/CLI settings. Composed from typed section configs defined in `agent::config`.
|
||||
@@ -62,7 +62,6 @@ pub fn get_mcp_server_config_with_project(
|
||||
name: &str,
|
||||
cwd: &std::path::Path,
|
||||
) -> Option<McpServerConfig> {
|
||||
// Check project-scoped configs from cwd (highest priority) to repo root
|
||||
let project_configs = crate::config::find_project_configs(cwd);
|
||||
for config_path in project_configs.iter().rev() {
|
||||
if let Ok(root) = crate::config::load_config_file(config_path) {
|
||||
@@ -73,7 +72,6 @@ pub fn get_mcp_server_config_with_project(
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to global config
|
||||
get_mcp_server_config(name)
|
||||
}
|
||||
|
||||
@@ -167,7 +165,6 @@ pub fn worktree_pool_from_toml(root: &TomlValue) -> PoolConfig {
|
||||
if let TomlValue::Table(table) = root
|
||||
&& let Some(pool_val) = table.get("worktree_pool")
|
||||
{
|
||||
// Try to deserialize the section; fall back to defaults on error
|
||||
pool_val
|
||||
.clone()
|
||||
.try_into::<PoolConfig>()
|
||||
@@ -635,9 +632,7 @@ fn parse_mcp_servers_from_toml(root: &TomlValue) -> IndexMap<String, McpServerCo
|
||||
result
|
||||
}
|
||||
|
||||
// ── .mcp.json support ────────────────────────────────────────────────
|
||||
|
||||
// `.mcp.json` discovery moved to `kigi-workspace` (client-side, shared with
|
||||
// `.mcp.json` discovery lives in `kigi-workspace` (client-side, shared with
|
||||
// the folder-trust gate); re-exported so `crate::util::config::*` paths keep working.
|
||||
pub use kigi_workspace::project_config::{
|
||||
MCP_JSON_FILENAME, find_mcp_json_files, mcp_json_candidate_paths,
|
||||
@@ -666,7 +661,7 @@ pub(crate) fn load_mcp_json_servers_as_configs(
|
||||
|
||||
/// Like [`load_mcp_json_servers_as_configs`] but bypasses the import-marker
|
||||
/// gate. Used by the `/import-claude` scanner so users can re-import items
|
||||
/// they previously skipped, even after the runtime cutoff is active.
|
||||
/// they had skipped, even after the runtime cutoff is active.
|
||||
pub fn load_mcp_json_servers_as_configs_unfiltered(
|
||||
cwd: &std::path::Path,
|
||||
) -> IndexMap<String, McpServerConfig> {
|
||||
@@ -777,7 +772,7 @@ pub(crate) fn load_claude_json_mcp_servers_as_configs(
|
||||
|
||||
/// Like [`load_claude_json_mcp_servers_as_configs`] but bypasses the
|
||||
/// import-marker gate. Used by the `/import-claude` scanner so users can
|
||||
/// re-import items they previously skipped, even after the runtime cutoff
|
||||
/// re-import items they had skipped, even after the runtime cutoff
|
||||
/// is active.
|
||||
pub fn load_claude_json_mcp_servers_as_configs_unfiltered(
|
||||
cwd: &std::path::Path,
|
||||
@@ -986,7 +981,6 @@ fn load_claude_json_mcp_servers_from(
|
||||
servers
|
||||
}
|
||||
|
||||
/// Read and parse a JSON file. Returns `None` on I/O or parse errors (logged).
|
||||
/// Env var carrying `--mcp-config-file` paths across the TUI → shell
|
||||
/// process boundary (joined with the platform path separator). kimi-cli
|
||||
/// parity (F6): each file uses the `.mcp.json` shape
|
||||
@@ -1013,6 +1007,7 @@ pub(crate) fn load_cli_mcp_config_files_as_configs() -> IndexMap<String, McpServ
|
||||
result
|
||||
}
|
||||
|
||||
/// Read and parse a JSON file. Returns `None` on I/O or parse errors (logged).
|
||||
pub(crate) fn read_mcp_json(path: &std::path::Path) -> Option<McpConfig> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(|e| {
|
||||
@@ -1026,7 +1021,7 @@ pub(crate) fn read_mcp_json(path: &std::path::Path) -> Option<McpConfig> {
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Like `load_mcp_servers_with_project` but returns raw configs without filtering by `enabled`.
|
||||
/// Like [`load_mcp_server_configs_with_project`] but returns raw configs without filtering by `enabled`.
|
||||
fn load_all_mcp_configs(cwd: &std::path::Path) -> IndexMap<String, McpServerConfig> {
|
||||
load_mcp_server_configs_with_project(cwd)
|
||||
.into_iter()
|
||||
@@ -1273,7 +1268,6 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
/// Covers all canonical wire values plus the unknown/corrupt fallback.
|
||||
#[test]
|
||||
fn test_parse_mcp_servers_empty() {
|
||||
let root = toml::from_str::<TomlValue>("").unwrap();
|
||||
@@ -1305,7 +1299,6 @@ args = ["server.js"]
|
||||
|
||||
#[test]
|
||||
fn test_use_leader_parsing_true() {
|
||||
// Test that we can parse a config with use_leader = true
|
||||
let toml_str = r#"
|
||||
[cli]
|
||||
use_leader = true
|
||||
@@ -1316,7 +1309,6 @@ use_leader = true
|
||||
|
||||
#[test]
|
||||
fn test_use_leader_parsing_false() {
|
||||
// Test that we can parse a config with use_leader = false
|
||||
let toml_str = r#"
|
||||
[cli]
|
||||
use_leader = false
|
||||
@@ -1327,7 +1319,6 @@ use_leader = false
|
||||
|
||||
#[test]
|
||||
fn test_use_leader_default_false() {
|
||||
// Test that missing use_leader defaults to false
|
||||
let toml_str = r#"
|
||||
[cli]
|
||||
auto_update = true
|
||||
@@ -1338,7 +1329,6 @@ auto_update = true
|
||||
|
||||
#[test]
|
||||
fn test_use_leader_no_cli_section() {
|
||||
// Test with no cli section at all
|
||||
let toml_str = r#"
|
||||
[models]
|
||||
default = "kigi-code-fast-1"
|
||||
@@ -1348,7 +1338,6 @@ default = "kigi-code-fast-1"
|
||||
let has_cli = table.get("cli").is_some();
|
||||
assert!(!has_cli);
|
||||
}
|
||||
// use_leader_from_toml() should default to false when no cli section
|
||||
assert!(!use_leader_from_toml(&root));
|
||||
}
|
||||
|
||||
@@ -1392,10 +1381,8 @@ default = "kigi-code-fast-1"
|
||||
assert_eq!(use_leader_from_toml_opt(&root), None);
|
||||
}
|
||||
|
||||
// WorktreeType tests
|
||||
#[test]
|
||||
fn test_project_scoped_mcp_override_replaces_entirely() {
|
||||
// Simulate global config with timeouts
|
||||
let global_toml = r#"
|
||||
[mcp_servers.linear]
|
||||
command = "npx"
|
||||
@@ -1407,7 +1394,6 @@ tool_timeout_sec = 60
|
||||
let global_root = toml::from_str::<TomlValue>(global_toml).unwrap();
|
||||
let global_servers = parse_mcp_servers_from_toml(&global_root);
|
||||
|
||||
// Simulate project config WITHOUT timeouts
|
||||
let project_toml = r#"
|
||||
[mcp_servers.linear]
|
||||
command = "npx"
|
||||
@@ -1417,17 +1403,14 @@ enabled = true
|
||||
let project_root = toml::from_str::<TomlValue>(project_toml).unwrap();
|
||||
let project_servers = parse_mcp_servers_from_toml(&project_root);
|
||||
|
||||
// Global config should have timeouts
|
||||
let global_linear = global_servers.get("linear").unwrap();
|
||||
assert_eq!(global_linear.startup_timeout_sec, Some(10));
|
||||
assert_eq!(global_linear.tool_timeout_sec, Some(60));
|
||||
|
||||
// Project config should NOT have timeouts (defaults apply)
|
||||
let project_linear = project_servers.get("linear").unwrap();
|
||||
assert_eq!(project_linear.startup_timeout_sec, None);
|
||||
assert_eq!(project_linear.tool_timeout_sec, None);
|
||||
|
||||
// Merge: project overrides global entirely
|
||||
let mut merged: IndexMap<String, McpServerConfig> = IndexMap::new();
|
||||
for (name, config) in &global_servers {
|
||||
merged.insert(name.clone(), config.clone());
|
||||
@@ -1436,7 +1419,6 @@ enabled = true
|
||||
merged.insert(name.clone(), config.clone());
|
||||
}
|
||||
|
||||
// After merge, the project config should have replaced the global one entirely
|
||||
let merged_linear = merged.get("linear").unwrap();
|
||||
assert_eq!(merged_linear.startup_timeout_sec, None);
|
||||
assert_eq!(merged_linear.tool_timeout_sec, None);
|
||||
@@ -1462,7 +1444,6 @@ enabled = true
|
||||
let project_root = toml::from_str::<TomlValue>(project_toml).unwrap();
|
||||
let project_servers = parse_mcp_servers_from_toml(&project_root);
|
||||
|
||||
// Merge: project adds new server
|
||||
let mut merged: IndexMap<String, McpServerConfig> = IndexMap::new();
|
||||
for (name, config) in &global_servers {
|
||||
merged.insert(name.clone(), config.clone());
|
||||
@@ -1488,7 +1469,6 @@ enabled = true
|
||||
let global_servers = parse_mcp_servers_from_toml(&global_root);
|
||||
assert!(global_servers.get("linear").unwrap().enabled);
|
||||
|
||||
// Project config disables the server
|
||||
let project_toml = r#"
|
||||
[mcp_servers.linear]
|
||||
command = "npx"
|
||||
@@ -1506,7 +1486,6 @@ enabled = false
|
||||
merged.insert(name.clone(), config.clone());
|
||||
}
|
||||
|
||||
// After merge, the server should be disabled by project config
|
||||
assert!(!merged.get("linear").unwrap().enabled);
|
||||
}
|
||||
|
||||
@@ -1557,7 +1536,6 @@ enabled = true
|
||||
let global_root = toml::from_str::<TomlValue>(global_toml).unwrap();
|
||||
let global_servers = parse_mcp_servers_from_toml(&global_root);
|
||||
|
||||
// Project only overrides linear
|
||||
let project_toml = r#"
|
||||
[mcp_servers.linear]
|
||||
command = "npx"
|
||||
@@ -1575,7 +1553,6 @@ enabled = false
|
||||
merged.insert(name.clone(), config.clone());
|
||||
}
|
||||
|
||||
// buildkite should be preserved from global config
|
||||
assert_eq!(merged.len(), 2);
|
||||
assert!(merged.get("buildkite").unwrap().enabled);
|
||||
assert!(!merged.get("linear").unwrap().enabled);
|
||||
@@ -1752,6 +1729,4 @@ enabled = false
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// === merge_section tests ===
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub use load::*;
|
||||
pub use mcp::*;
|
||||
pub use permissions::*;
|
||||
pub use persist::*;
|
||||
// `remote` extracted to the `kigi-config-types` crate (dependency inversion);
|
||||
// `remote` lives in the `kigi-config-types` crate (dependency inversion);
|
||||
// re-exported so `crate::util::config::{RemoteSettings, GoalRoleModel}` keep working.
|
||||
pub use kigi_config_types::{
|
||||
CampaignOverride, ContextualHintsRemote, DisplayRefreshSettings, DoomLoopRecoverySettings,
|
||||
|
||||
@@ -267,7 +267,6 @@ pub fn load_require_plan_approval() -> bool {
|
||||
}
|
||||
|
||||
/// Synchronously load the remote agent secret from the config file.
|
||||
/// Looks for [remote] section with secret field.
|
||||
///
|
||||
/// Example config.toml:
|
||||
/// ```toml
|
||||
@@ -485,7 +484,7 @@ mod tests {
|
||||
|
||||
// Table-driven: (cli_yolo, cli_perm_mode, config_yolo, expected_yolo, description)
|
||||
let cases: &[(bool, Option<&str>, bool, bool, &str)] = &[
|
||||
// --- CLI --permission-mode present: it wins completely ---
|
||||
// CLI --permission-mode present: it wins completely.
|
||||
(
|
||||
false,
|
||||
Some("plan"),
|
||||
@@ -558,7 +557,7 @@ mod tests {
|
||||
true,
|
||||
"bypass + --yolo still yolo",
|
||||
),
|
||||
// --- No --permission-mode: fall back to legacy --yolo then config ---
|
||||
// No --permission-mode: fall back to legacy --yolo then config.
|
||||
(true, None, false, true, "--yolo alone → yolo"),
|
||||
(true, None, true, true, "--yolo + config yolo → yolo"),
|
||||
(false, None, true, true, "no cli flags + config yolo → yolo"),
|
||||
|
||||
@@ -173,10 +173,6 @@ fn merge_ask_user_question_section(
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge serialized fields of `value` into `table[key]`, preserving any
|
||||
/// existing keys not present in the serialized output. This prevents
|
||||
/// unmodeled fields (e.g. pager-written `show_timestamps`, `auto_dark_theme`)
|
||||
/// from being silently dropped when `save_config` round-trips the struct.
|
||||
/// Deep-merge `incoming` into `existing`: nested tables recurse; scalars replace.
|
||||
fn merge_toml_tables(
|
||||
existing: &mut TomlMap<String, TomlValue>,
|
||||
@@ -194,6 +190,10 @@ fn merge_toml_tables(
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge serialized fields of `value` into `table[key]`, preserving any
|
||||
/// existing keys not present in the serialized output. This prevents
|
||||
/// unmodeled fields (e.g. pager-written `show_timestamps`, `auto_dark_theme`)
|
||||
/// from being silently dropped when `save_config` round-trips the struct.
|
||||
fn merge_section<T: serde::Serialize>(
|
||||
table: &mut TomlMap<String, TomlValue>,
|
||||
key: &str,
|
||||
@@ -344,8 +344,6 @@ mod tests {
|
||||
assert!(!oauth.contains_key("plain"));
|
||||
}
|
||||
|
||||
// -- Cursor MCP loading --
|
||||
|
||||
#[test]
|
||||
fn merge_section_preserves_unmodeled_fields() {
|
||||
let mut table = TomlMap::new();
|
||||
@@ -479,20 +477,13 @@ mod tests {
|
||||
#[test]
|
||||
fn merge_section_session_default_does_not_leak_load_envrc() {
|
||||
let mut table = TomlMap::new();
|
||||
// The starting state: user has no [session] section on disk
|
||||
// (managed config might set load_envrc = false; user TOML is
|
||||
// silent on the matter).
|
||||
assert!(table.get("session").is_none());
|
||||
|
||||
// Pager calls update_config to set a totally unrelated [ui]
|
||||
// field. The closure exits with cfg.session ==
|
||||
// SessionConfig::default() (both fields None). save_config
|
||||
// then calls merge_section("session", &cfg.session).
|
||||
let cfg = crate::agent::config::SessionConfig::default();
|
||||
merge_section(&mut table, "session", &cfg);
|
||||
|
||||
// After the fix, [session] is either absent OR present-but-
|
||||
// empty. Crucially, it must NOT contain `load_envrc`.
|
||||
// [session] must be either absent or present-but-empty; it must
|
||||
// NOT contain `load_envrc`.
|
||||
if let Some(session) = table.get("session").and_then(|v| v.as_table()) {
|
||||
assert!(
|
||||
session.get("load_envrc").is_none(),
|
||||
@@ -506,7 +497,6 @@ mod tests {
|
||||
"default auto_compact_threshold_percent must not be serialized either"
|
||||
);
|
||||
}
|
||||
// If the table is wholly absent or empty, that's also fine.
|
||||
}
|
||||
|
||||
/// Companion to the above: when the user explicitly commits a
|
||||
@@ -522,9 +512,7 @@ mod tests {
|
||||
session.insert("load_envrc".into(), TomlValue::Boolean(false));
|
||||
table.insert("session".into(), TomlValue::Table(session));
|
||||
|
||||
// User commits auto_compact_threshold_percent via the modal.
|
||||
// The cfg.session has load_envrc: None (user never touched it)
|
||||
// and auto_compact_threshold_percent: Some(70).
|
||||
// load_envrc: None means the user never touched it.
|
||||
let cfg = crate::agent::config::SessionConfig {
|
||||
auto_compact_threshold_percent: Some(70),
|
||||
load_envrc: None,
|
||||
@@ -532,7 +520,6 @@ mod tests {
|
||||
merge_section(&mut table, "session", &cfg);
|
||||
|
||||
let session = table.get("session").unwrap().as_table().unwrap();
|
||||
// The user's commit landed.
|
||||
assert_eq!(
|
||||
session
|
||||
.get("auto_compact_threshold_percent")
|
||||
@@ -571,8 +558,6 @@ mod tests {
|
||||
"explicit load_envrc = false on disk must load as Some(false), not None"
|
||||
);
|
||||
|
||||
// Now round-trip through save: merge into a fresh table and
|
||||
// verify load_envrc = false stays present.
|
||||
let mut table = TomlMap::new();
|
||||
merge_section(&mut table, "session", &cfg.session);
|
||||
let session = table.get("session").unwrap().as_table().unwrap();
|
||||
@@ -646,12 +631,10 @@ auto_light_theme = "kigiday"
|
||||
|
||||
#[test]
|
||||
fn ui_config_hunk_tracker_mode_round_trips() {
|
||||
// Parse from `[ui].hunk_tracker_mode`...
|
||||
let root: TomlValue = toml::from_str("[ui]\nhunk_tracker_mode = \"off\"\n").unwrap();
|
||||
let cfg = load_config_from_toml(&root);
|
||||
assert_eq!(cfg.ui.hunk_tracker_mode.as_deref(), Some("off"));
|
||||
|
||||
// ...and serialize back through merge_section.
|
||||
let mut table = root.as_table().unwrap().clone();
|
||||
merge_section(&mut table, "ui", &cfg.ui);
|
||||
let ui = table.get("ui").unwrap().as_table().unwrap();
|
||||
@@ -739,7 +722,6 @@ auto_update = true
|
||||
// User changes default model (unrelated to UI)
|
||||
cfg.models.default = Some("kigi-4".to_string());
|
||||
|
||||
// Simulate save_config
|
||||
let mut table = root.as_table().unwrap().clone();
|
||||
merge_section(&mut table, "cli", &cfg.cli);
|
||||
merge_section(&mut table, "models", &cfg.models);
|
||||
@@ -779,7 +761,6 @@ auto_update = true
|
||||
ui.insert("compact_mode".into(), TomlValue::Boolean(true));
|
||||
table.insert("ui".into(), TomlValue::Table(ui));
|
||||
|
||||
// Revert both to false (their defaults)
|
||||
let cfg = crate::agent::config::UiConfig::default();
|
||||
merge_section(&mut table, "ui", &cfg);
|
||||
|
||||
@@ -973,8 +954,6 @@ auto_update = true
|
||||
assert_eq!(cfg2.models.default.as_deref(), Some("kigi-persisted"));
|
||||
}
|
||||
|
||||
// ── merge_section pin tests for CLI/session setters ──────────────────
|
||||
//
|
||||
// Pin the schema-level write shape: each setter writes to the correct
|
||||
// TOML section, and `None` fields don't serialize (skip_serializing_if).
|
||||
|
||||
@@ -1095,7 +1074,7 @@ auto_update = true
|
||||
);
|
||||
}
|
||||
|
||||
// ── resolve_auto_compact_threshold_percent: precedence matrix ──────────
|
||||
// resolve_auto_compact_threshold_percent: precedence matrix.
|
||||
//
|
||||
// Covers every boundary in the resolver chain:
|
||||
// env > user [model.<id>] > user [session] > GB per-model > GB global > 85
|
||||
@@ -1194,8 +1173,6 @@ auto_update = true
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 6: default (all unset) ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn all_unset_returns_default_85() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1213,8 +1190,6 @@ auto_update = true
|
||||
);
|
||||
}
|
||||
|
||||
// ── Tier 5: GB global ───────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn gb_global_only() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1222,8 +1197,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), 40);
|
||||
}
|
||||
|
||||
// ── Tier 4 > Tier 5: GB per-model beats GB global ───────────────
|
||||
|
||||
#[test]
|
||||
fn gb_per_model_beats_gb_global() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1231,8 +1204,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, Some(90)), 90);
|
||||
}
|
||||
|
||||
// ── Tier 3 > Tier 4: user global beats GB per-model ─────────────
|
||||
|
||||
#[test]
|
||||
fn user_session_beats_gb_per_model() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1247,8 +1218,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), 75);
|
||||
}
|
||||
|
||||
// ── Tier 2 > Tier 3: user per-model beats user global ───────────
|
||||
|
||||
#[test]
|
||||
fn user_per_model_beats_user_session() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1277,8 +1246,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, Some(90)), 70);
|
||||
}
|
||||
|
||||
// ── Tier 1: env wins over everything ────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn env_beats_user_per_model() {
|
||||
let _g = EnvVarGuard::set("50");
|
||||
@@ -1300,8 +1267,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), 100);
|
||||
}
|
||||
|
||||
// ── Env-tier failure modes fall through ─────────────────────────
|
||||
|
||||
#[test]
|
||||
fn env_out_of_range_high_falls_through() {
|
||||
let _g = EnvVarGuard::set("101");
|
||||
@@ -1330,8 +1295,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), DEFAULT_AUTO_COMPACT_THRESHOLD_PERCENT);
|
||||
}
|
||||
|
||||
// ── Per-model entry for a DIFFERENT model must not match ────────
|
||||
|
||||
#[test]
|
||||
fn user_per_model_for_other_model_does_not_match() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1362,8 +1325,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), 75);
|
||||
}
|
||||
|
||||
// ── ModelInfo-less call still walks the rest of the chain ───────
|
||||
|
||||
#[test]
|
||||
fn missing_model_info_falls_through_to_gb_global() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1374,8 +1335,6 @@ auto_update = true
|
||||
);
|
||||
}
|
||||
|
||||
// ── No remote_settings still works ──────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn no_remote_settings_falls_through_to_default() {
|
||||
let _g = EnvVarGuard::unset();
|
||||
@@ -1386,8 +1345,6 @@ auto_update = true
|
||||
assert_eq!(resolve(&cfg, None), DEFAULT_AUTO_COMPACT_THRESHOLD_PERCENT);
|
||||
}
|
||||
|
||||
// ── ConfigModelOverride should NOT collapse into ModelInfo ──────
|
||||
//
|
||||
// Regression guard. If `ConfigModelOverride::apply` ever starts
|
||||
// merging `auto_compact_threshold_percent` into `ModelInfo`, the
|
||||
// resolver would see user-per-model as GB-per-model and ordering
|
||||
|
||||
@@ -11,8 +11,7 @@ pub(crate) const ENV_AUTO_PERMISSION_MODE: &str = "KIGI_AUTO_PERMISSION_MODE";
|
||||
#[cfg(test)]
|
||||
pub(crate) static AUTO_PERMISSION_MODE_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
/// Extract the `[auto_mode] enabled` gate from one TOML layer (the local opt-in
|
||||
/// that replaced `[features] auto_permission_mode`).
|
||||
/// Extract the `[auto_mode] enabled` gate from one TOML layer.
|
||||
fn auto_permission_mode_from_toml(v: Option<&TomlValue>) -> Option<bool> {
|
||||
v?.get("auto_mode")?.get("enabled")?.as_bool()
|
||||
}
|
||||
|
||||
@@ -134,9 +134,13 @@ mod compaction_wall_clock_budget_tests {
|
||||
// Assumes KIGI_COMPACTION_WALL_CLOCK_SECS is unset in the test env.
|
||||
#[test]
|
||||
fn default_global_disable_and_no_clamp() {
|
||||
assert_eq!(resolve(None), 300); // client default
|
||||
assert_eq!(resolve(Some(450)), 450); // server global wins
|
||||
assert_eq!(resolve(Some(0)), 0); // 0 explicitly disables (no clamp)
|
||||
assert_eq!(resolve(Some(5)), 5); // low values pass through (warned, not clamped)
|
||||
// client default
|
||||
assert_eq!(resolve(None), 300);
|
||||
// server global wins
|
||||
assert_eq!(resolve(Some(450)), 450);
|
||||
// 0 explicitly disables (no clamp)
|
||||
assert_eq!(resolve(Some(0)), 0);
|
||||
// low values pass through (warned, not clamped)
|
||||
assert_eq!(resolve(Some(5)), 5);
|
||||
}
|
||||
}
|
||||
@@ -258,18 +258,22 @@ mod mcp_startup_timeout_tests {
|
||||
#[test]
|
||||
fn precedence_requirements_env_config_remote_default() {
|
||||
assert_eq!(r(None, None, None, None), DEFAULT_MCP_STARTUP_TIMEOUT_SECS);
|
||||
assert_eq!(r(Some(5), Some(6), Some(7), Some(8)), 5); // requirements highest
|
||||
assert_eq!(r(None, Some(6), Some(7), Some(8)), 6); // env
|
||||
assert_eq!(r(None, None, Some(7), Some(8)), 7); // config
|
||||
assert_eq!(r(None, None, None, Some(8)), 8); // remote
|
||||
// requirements highest
|
||||
assert_eq!(r(Some(5), Some(6), Some(7), Some(8)), 5);
|
||||
// env
|
||||
assert_eq!(r(None, Some(6), Some(7), Some(8)), 6);
|
||||
// config
|
||||
assert_eq!(r(None, None, Some(7), Some(8)), 7);
|
||||
// remote
|
||||
assert_eq!(r(None, None, None, Some(8)), 8);
|
||||
}
|
||||
}
|
||||
|
||||
// ── MCP max output bytes (inline tool-result cap) ───────────────────────────
|
||||
// MCP max output bytes (inline tool-result cap).
|
||||
//
|
||||
// Full multi-tier resolve lives only here (shell can read config/requirements).
|
||||
// Tools holds a single effective atomic: we resolve once on apply and push the
|
||||
// result via `set_mcp_max_output_bytes` so free-function truncation sees it.
|
||||
// Tools holds a single effective atomic: resolved once on apply and pushed via
|
||||
// `set_mcp_max_output_bytes` so free-function truncation sees it.
|
||||
|
||||
/// Default MCP tool-result inline cap (bytes).
|
||||
pub const DEFAULT_MAX_MCP_OUTPUT_BYTES: usize = kigi_tools::MCP_MAX_OUTPUT_BYTES;
|
||||
@@ -318,7 +322,8 @@ pub fn resolve_max_mcp_output_bytes(remote: Option<u64>) -> usize {
|
||||
resolve_max_mcp_output_bytes_precedence(
|
||||
requirements,
|
||||
kigi_tools::mcp_max_output_bytes_from_env(),
|
||||
None, // project tier needs a cwd — see resolve_max_mcp_output_bytes_for_cwd
|
||||
// project tier needs a cwd — see resolve_max_mcp_output_bytes_for_cwd
|
||||
None,
|
||||
config,
|
||||
remote_usize,
|
||||
)
|
||||
@@ -400,11 +405,16 @@ mod max_mcp_output_bytes_tests {
|
||||
r(None, None, None, None, None),
|
||||
DEFAULT_MAX_MCP_OUTPUT_BYTES
|
||||
);
|
||||
assert_eq!(r(Some(1), Some(2), Some(9), Some(3), Some(4)), 1); // requirements highest
|
||||
assert_eq!(r(None, Some(2), Some(9), Some(3), Some(4)), 2); // env beats project
|
||||
assert_eq!(r(None, None, Some(9), Some(3), Some(4)), 9); // project beats user config
|
||||
assert_eq!(r(None, None, None, Some(3), Some(4)), 3); // user config beats remote
|
||||
assert_eq!(r(None, None, None, None, Some(4)), 4); // remote beats default
|
||||
// requirements highest
|
||||
assert_eq!(r(Some(1), Some(2), Some(9), Some(3), Some(4)), 1);
|
||||
// env beats project
|
||||
assert_eq!(r(None, Some(2), Some(9), Some(3), Some(4)), 2);
|
||||
// project beats user config
|
||||
assert_eq!(r(None, None, Some(9), Some(3), Some(4)), 9);
|
||||
// user config beats remote
|
||||
assert_eq!(r(None, None, None, Some(3), Some(4)), 3);
|
||||
// remote beats default
|
||||
assert_eq!(r(None, None, None, None, Some(4)), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -440,11 +440,16 @@ mod ask_user_question_timeout_tests {
|
||||
let d = kigi_tools::implementations::kigi::ask_user_question::RESPONSE_TIMEOUT.as_secs();
|
||||
let r = resolve_ask_user_question_timeout_secs_from_tiers;
|
||||
assert_eq!(r(None, None, None, None, None), d);
|
||||
assert_eq!(r(Some(1), Some(2), Some(3), Some(4), Some(5)), 1); // requirements highest
|
||||
assert_eq!(r(None, Some(2), Some(3), Some(4), Some(5)), 2); // env
|
||||
assert_eq!(r(None, None, Some(3), Some(4), Some(5)), 3); // user config
|
||||
assert_eq!(r(None, None, None, Some(4), Some(5)), 4); // managed
|
||||
assert_eq!(r(None, None, None, None, Some(5)), 5); // remote
|
||||
// requirements highest
|
||||
assert_eq!(r(Some(1), Some(2), Some(3), Some(4), Some(5)), 1);
|
||||
// env
|
||||
assert_eq!(r(None, Some(2), Some(3), Some(4), Some(5)), 2);
|
||||
// user config
|
||||
assert_eq!(r(None, None, Some(3), Some(4), Some(5)), 3);
|
||||
// managed
|
||||
assert_eq!(r(None, None, None, Some(4), Some(5)), 4);
|
||||
// remote
|
||||
assert_eq!(r(None, None, None, None, Some(5)), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -516,14 +521,17 @@ mod tests {
|
||||
let req: TomlValue = toml::from_str("[toolset.bash]\nfind_bfs = true\n").unwrap();
|
||||
let managed: TomlValue = toml::from_str("[toolset.bash]\ngrep_ugrep = false\n").unwrap();
|
||||
let (find, grep) = resolve_search_tools_enabled(Some(&req), Some(&user), Some(&managed));
|
||||
assert!(find); // requirements `true` beats user `false`
|
||||
assert!(grep); // user `true` beats managed `false`
|
||||
// requirements `true` beats user `false`
|
||||
assert!(find);
|
||||
// user `true` beats managed `false`
|
||||
assert!(grep);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_search_tool_enabled_precedence() {
|
||||
// args: disable, requirement, env, config, managed
|
||||
assert!(resolve_search_tool_enabled(None, None, None, None, None)); // default on
|
||||
// default on
|
||||
assert!(resolve_search_tool_enabled(None, None, None, None, None));
|
||||
// Org requirement wins outright — even over the user DISABLE kill-switch.
|
||||
assert!(resolve_search_tool_enabled(
|
||||
Some(true),
|
||||
|
||||
@@ -1,100 +1,82 @@
|
||||
use super::persist::update_config;
|
||||
use anyhow::Result;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings helpers — typed disk-write wrappers for each setting.
|
||||
// All route through `update_config` → `merge_section` → `save_config`.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings helpers — typed disk-write wrappers for each setting. All route
|
||||
// through `update_config` → `merge_section` → `save_config`.
|
||||
|
||||
/// Persist `[ui].compact_mode` via `update_config`.
|
||||
pub async fn set_compact_mode(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.compact_mode = value).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].show_timestamps` via `update_config`. `UiConfig::show_timestamps`
|
||||
/// is `Option<bool>` — pager-side `None` means "use default" — so we wrap.
|
||||
/// `UiConfig::show_timestamps` is `Option<bool>` — pager-side `None` means
|
||||
/// "use default" — so we wrap.
|
||||
pub async fn set_show_timestamps(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.show_timestamps = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].show_timeline` via `update_config`. Same `Option<bool>`
|
||||
/// shape as `show_timestamps`.
|
||||
pub async fn set_show_timeline(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.show_timeline = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].simple_mode` via `update_config`. Same `Option<bool>`
|
||||
/// shape as `show_timestamps`.
|
||||
pub async fn set_simple_mode(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.simple_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].undo` via `update_config`. The nested struct
|
||||
/// stays out of `config.toml` until a tip is toggled (`skip_serializing_if`).
|
||||
/// The nested `contextual_hints` struct stays out of `config.toml` until a
|
||||
/// tip is toggled (`skip_serializing_if`).
|
||||
pub async fn set_contextual_hint_undo(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.undo = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].plan_mode` via `update_config`.
|
||||
pub async fn set_contextual_hint_plan_mode(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.plan_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].image_input` via `update_config`.
|
||||
pub async fn set_contextual_hint_image_input(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.image_input = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].send_now` via `update_config`.
|
||||
pub async fn set_contextual_hint_send_now(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.send_now = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].small_screen` via `update_config`.
|
||||
pub async fn set_contextual_hint_small_screen(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.small_screen = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.contextual_hints].word_select` via `update_config`.
|
||||
pub async fn set_contextual_hint_word_select(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.contextual_hints.word_select = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].theme` via `update_config`. Caller must pass the
|
||||
/// canonical theme name (`kiginight`, `tokyonight`, `auto`, etc.).
|
||||
/// Caller must pass the canonical theme name (`kiginight`, `tokyonight`,
|
||||
/// `auto`, etc.).
|
||||
pub async fn set_theme(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.theme = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].auto_dark_theme` via `update_config`. `UiConfig::auto_dark_theme`
|
||||
/// is `Option<String>` (canonical theme name; `auto` is rejected by the
|
||||
/// pager's `load_auto_theme_config` filter at read time to prevent
|
||||
/// circular reference).
|
||||
/// `auto` is rejected by the pager's `load_auto_theme_config` filter at read
|
||||
/// time to prevent circular reference.
|
||||
pub async fn set_auto_dark_theme(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.auto_dark_theme = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].auto_light_theme` via `update_config`. Same shape as
|
||||
/// [`set_auto_dark_theme`].
|
||||
pub async fn set_auto_light_theme(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.auto_light_theme = Some(value)).await
|
||||
}
|
||||
|
||||
/// Maximum length (in bytes) accepted by [`set_default_model`].
|
||||
/// Defense against callers bypassing catalog validation.
|
||||
pub const MAX_DEFAULT_MODEL_LEN: usize = 256;
|
||||
|
||||
/// Persist `[models].default` and dismiss any active campaign nudging it (an
|
||||
/// explicit user pick wins over the soft campaign default).
|
||||
///
|
||||
/// This is the only sanctioned writer of `models.default`; it routes through
|
||||
/// [`super::campaigns::persist_models_default`] so a user pick always dismisses
|
||||
/// an active campaign. Do not persist `models.default` via raw `update_config`,
|
||||
/// or a campaign would keep overriding the user's choice.
|
||||
/// [`super::campaigns::persist_models_default`] so an explicit user pick
|
||||
/// always dismisses an active campaign nudging that default. Do not persist
|
||||
/// `models.default` via raw `update_config`, or a campaign would keep
|
||||
/// overriding the user's choice.
|
||||
///
|
||||
/// Caller must validate `value` against the model catalog first.
|
||||
/// Empty string clears the field (falls back to remote/built-in default).
|
||||
/// Length over [`MAX_DEFAULT_MODEL_LEN`] returns `Err`.
|
||||
/// Length over [`MAX_DEFAULT_MODEL_LEN`] returns `Err` (checked in
|
||||
/// `persist_models_default`).
|
||||
pub async fn set_default_model(value: String) -> Result<()> {
|
||||
super::campaigns::persist_models_default(
|
||||
if value.is_empty() { None } else { Some(value) },
|
||||
@@ -103,10 +85,8 @@ pub async fn set_default_model(value: String) -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Persist `[ui].fork_secondary_model` via `update_config`.
|
||||
///
|
||||
/// Caller must validate against the model catalog. Empty string
|
||||
/// restores the built-in default. Length > [`MAX_DEFAULT_MODEL_LEN`] → `Err`.
|
||||
/// restores the built-in default.
|
||||
pub async fn set_fork_secondary_model(value: String) -> Result<()> {
|
||||
if value.len() > MAX_DEFAULT_MODEL_LEN {
|
||||
anyhow::bail!(
|
||||
@@ -130,81 +110,64 @@ pub async fn set_fork_secondary_model(value: String) -> Result<()> {
|
||||
const MAX_THOUGHTS_WIDTH_SHELL_MIN: i64 = 40;
|
||||
const MAX_THOUGHTS_WIDTH_SHELL_MAX: i64 = 500;
|
||||
|
||||
/// Persist `[ui].max_thoughts_width` via `update_config`.
|
||||
/// Defensively clamps to `[40, 500]` at the shell boundary.
|
||||
pub async fn set_max_thoughts_width(value: i64) -> Result<()> {
|
||||
let clamped = value.clamp(MAX_THOUGHTS_WIDTH_SHELL_MIN, MAX_THOUGHTS_WIDTH_SHELL_MAX) as u16;
|
||||
update_config(|cfg| cfg.ui.max_thoughts_width = clamped).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].scroll_speed` via `update_config`.
|
||||
/// Defensively clamps to `[1, 100]` at the shell boundary.
|
||||
pub async fn set_scroll_speed(value: i64) -> Result<()> {
|
||||
let clamped = value.clamp(1, 100) as u8;
|
||||
update_config(|cfg| cfg.ui.scroll_speed = Some(clamped)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].scroll_mode` (`auto` | `wheel` | `trackpad`) via `update_config`.
|
||||
pub async fn set_scroll_mode(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.scroll_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].invert_scroll` via `update_config`.
|
||||
pub async fn set_invert_scroll(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.invert_scroll = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui.display_refresh].auto_cadence_enabled` via `update_config`.
|
||||
/// Nested field only — does not replace the whole `display_refresh` object.
|
||||
pub async fn set_display_refresh_auto_cadence(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.display_refresh.auto_cadence_enabled = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].scroll_lines` via `update_config`.
|
||||
/// Defensively clamps to `[1, 10]` at the shell boundary.
|
||||
pub async fn set_scroll_lines(value: i64) -> Result<()> {
|
||||
let clamped = value.clamp(1, 10) as u8;
|
||||
update_config(|cfg| cfg.ui.scroll_lines = Some(clamped)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].vim_mode` via `update_config`.
|
||||
pub async fn set_vim_mode(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.vim_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].remember_tool_approvals` via `update_config`.
|
||||
pub async fn set_remember_tool_approvals(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.remember_tool_approvals = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].show_thinking_blocks` via `update_config`.
|
||||
pub async fn set_show_thinking_blocks(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.show_thinking_blocks = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].prompt_suggestions` via `update_config`.
|
||||
pub async fn set_prompt_suggestions(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.prompt_suggestions = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[toolset.ask_user_question].timeout_enabled` via `update_config`
|
||||
/// (the user tier of the shell's tiered resolver; the effective value is
|
||||
/// re-resolved at agent build).
|
||||
/// The user tier of the shell's tiered resolver; the effective value is
|
||||
/// re-resolved at agent build.
|
||||
pub async fn set_ask_user_question_timeout_enabled(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ask_user_question.timeout_enabled = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].group_tool_verbs` via `update_config`.
|
||||
pub async fn set_group_tool_verbs(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.group_tool_verbs = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].collapsed_edit_blocks` via `update_config`.
|
||||
pub async fn set_collapsed_edit_blocks(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.collapsed_edit_blocks = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].keep_text_selection` (`flash` | `hold` | `word_select`).
|
||||
/// Clears the legacy `selection_highlight_duration_ms` and the retired
|
||||
/// `double_click_action` keys it supersedes so the two can never drift (one-shot
|
||||
/// disk migration away from the legacy key on any Settings write).
|
||||
@@ -217,43 +180,29 @@ pub async fn set_keep_text_selection(value: String) -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Persist `[ui].render_mermaid` via `update_config`. Value is one of the
|
||||
/// canonical strings `auto` | `on` | `off`.
|
||||
pub async fn set_render_mermaid(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.render_mermaid = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].hunk_tracker_mode` via `update_config`. Value is one of the
|
||||
/// canonical strings `agent_only` | `all_dirty` | `off`.
|
||||
/// Restart-required: the mode is read once at connect time.
|
||||
pub async fn set_hunk_tracker_mode(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.hunk_tracker_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].voice_capture_mode` via `update_config`. Value is one of the
|
||||
/// canonical strings `toggle` | `hold`.
|
||||
pub async fn set_voice_capture_mode(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.voice_capture_mode = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].voice_stt_language` via `update_config`. Value is a canonical
|
||||
/// language code from the settings catalog (`en`, `es`, …) or `auto` (system
|
||||
/// locale, falling back to English).
|
||||
/// `auto` means system locale, falling back to English.
|
||||
pub async fn set_voice_stt_language(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.voice_stt_language = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].default_selected_permission` via `update_config`. Value is
|
||||
/// one of the canonical strings from `DEFAULT_SELECTED_PERMISSION_CHOICES`
|
||||
/// (`default` | `allow_once` | `allow_always` | `reject`); `default` is the
|
||||
/// "no preselection" sentinel.
|
||||
/// `default` is the "no preselection" sentinel.
|
||||
pub async fn set_default_selected_permission(value: String) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.default_selected_permission = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].cancel_subagents_on_turn_cancel` via `update_config`.
|
||||
/// Canonical values: `ask` (clear / prompt each time), `always_stop`,
|
||||
/// `always_continue`.
|
||||
pub async fn set_cancel_subagents_on_turn_cancel(value: String) -> Result<()> {
|
||||
update_config(|cfg| {
|
||||
cfg.ui.cancel_subagents_on_turn_cancel = if value == "ask" { None } else { Some(value) };
|
||||
@@ -261,7 +210,6 @@ pub async fn set_cancel_subagents_on_turn_cancel(value: String) -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Persist `[ui].screen_mode` (`fullscreen` | `minimal`). Empty clears the key.
|
||||
pub async fn set_screen_mode(value: String) -> Result<()> {
|
||||
update_config(|cfg| {
|
||||
cfg.ui.screen_mode = if value.is_empty() { None } else { Some(value) };
|
||||
@@ -269,13 +217,11 @@ pub async fn set_screen_mode(value: String) -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Persist `[cli].show_tips` via `update_config`.
|
||||
/// Restart-required: `resolve_tips` reads this once at startup.
|
||||
pub async fn set_show_tips(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.cli.show_tips = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[cli].auto_update` via `update_config`.
|
||||
/// Restart-required: auto-update check fires once on startup.
|
||||
pub async fn set_auto_update(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.cli.auto_update = Some(value)).await
|
||||
|
||||
@@ -85,7 +85,6 @@ pub fn resolve_tips(
|
||||
let usr = user.and_then(tips_from_toml);
|
||||
let mgd = managed.and_then(tips_from_toml);
|
||||
|
||||
// Priority: requirements > remote > user > managed.
|
||||
merge_tips(req, usr, mgd, remote_tips)
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,6 @@ default = "kigi-code-fast-1"
|
||||
worktree_type = "invalid"
|
||||
"#;
|
||||
let root: TomlValue = toml::from_str(toml_str).unwrap();
|
||||
// Invalid values should fall back to default
|
||||
assert_eq!(worktree_type_from_toml(&root), WorktreeType::Linked);
|
||||
}
|
||||
|
||||
@@ -297,8 +296,6 @@ worktree_type = "invalid"
|
||||
);
|
||||
}
|
||||
|
||||
// === restore_code config tests ===
|
||||
|
||||
#[test]
|
||||
fn test_restore_code_from_toml_present_true() {
|
||||
let root: TomlValue = toml::from_str("[cli]\nrestore_code = true").unwrap();
|
||||
@@ -374,6 +371,4 @@ worktree_type = "invalid"
|
||||
};
|
||||
assert!(!resolve_restore_code(&root, Some(&remote)));
|
||||
}
|
||||
|
||||
// === minimum_version tests ===
|
||||
}
|
||||
@@ -59,7 +59,6 @@ impl KigiAuthCredentials {
|
||||
self.auth_manager = Some(am);
|
||||
self
|
||||
}
|
||||
/// Return a reference to the internal `AuthManager`, if any.
|
||||
pub fn auth_manager(&self) -> Option<&Arc<crate::auth::AuthManager>> {
|
||||
self.auth_manager.as_ref()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user