Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use rustyline::Editor;
use tokio::sync::Mutex;

use tinyharness_lib::{
config::load_merged_settings,
config::load_settings,
mode::AgentMode,
provider::{Message, Provider, Role},
Expand Down Expand Up @@ -314,8 +315,9 @@ pub async fn run_agent_loop(
let mut auto_accept = false;

loop {
// Filter tools based on current mode
let tools = tool_manager.tools_for_mode(ctx.current_mode);
// Filter tools based on current mode and settings
let (_, _, merged) = load_merged_settings();
let tools = tool_manager.tools_for_mode(ctx.current_mode, merged.auto_compact_enabled);
Comment thread
PTFOPlayer marked this conversation as resolved.

// Call the provider — it returns a receiver for streaming chunks
let mut recv = {
Expand Down
6 changes: 4 additions & 2 deletions src/agent/tui_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::sync::{
use tokio::sync::Mutex;

use tinyharness_lib::{
config::load_merged_settings,
config::load_settings,
provider::{Message, Provider, Role},
session::Session,
Expand Down Expand Up @@ -457,8 +458,9 @@ async fn process_user_message(
// Clear interrupt flag for this turn
interrupted.store(false, Ordering::SeqCst);

// Filter tools based on current mode
let tools = tool_manager.tools_for_mode(ctx.current_mode);
// Filter tools based on current mode and settings
let (_, _, merged) = load_merged_settings();
let tools = tool_manager.tools_for_mode(ctx.current_mode, merged.auto_compact_enabled);
Comment thread
PTFOPlayer marked this conversation as resolved.

// Call the provider
let mut recv = {
Expand Down
39 changes: 39 additions & 0 deletions src/commands/config_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,45 @@ pub fn execute_autoaccept(out: &mut Output, arg: Option<&str>) -> Result<Command
Ok(CommandResult::Ok)
}

// ── AutoCompact (sync — no provider access needed) ───────────────────────────

/// Execute the /autocompact command to toggle the auto_compact tool.
pub fn execute_autocompact(out: &mut Output, arg: Option<&str>) -> Result<CommandResult, String> {
let a = arg.unwrap_or("");

if a.is_empty() {
let settings = load_settings();
let (status, color) = if settings.auto_compact_enabled {
("on", GREEN)
} else {
("off", ORANGE)
};
let _ = writeln!(out, "{BOLD}Auto-compact: {color}{status}{RESET}",);
return Ok(CommandResult::Ok);
}
Comment thread
PTFOPlayer marked this conversation as resolved.

let new_value = match a.to_lowercase().as_str() {
"on" | "true" | "yes" | "1" => true,
"off" | "false" | "no" | "0" => false,
_ => {
return Err("Invalid value. Use 'on' or 'off', e.g. /autocompact off".to_string());
}
};

let mut settings = load_settings();
settings.auto_compact_enabled = new_value;
save_settings(&settings);

let (status, color) = if new_value {
("on", GREEN)
} else {
("off", ORANGE)
};
let _ = writeln!(out, "{BOLD}Auto-compact set to {color}{status}{RESET}",);

Comment thread
PTFOPlayer marked this conversation as resolved.
Ok(CommandResult::Ok)
}

// ── Think Type (async — needs provider.lock().await) ───────────────────────

async_command!(
Expand Down
6 changes: 6 additions & 0 deletions src/commands/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ fn dump_command_lists(file: &mut std::fs::File) {
let denied: Vec<String> = settings.denied_command_prefixes.clone().unwrap_or_default();

writeln!(file, "Auto-accept mode: {}", settings.auto_accept_mode).unwrap();
writeln!(
file,
"Auto-compact enabled: {}",
settings.auto_compact_enabled
)
.unwrap();
writeln!(file, "Safe command prefixes ({}):", safe.len()).unwrap();
for cmd in &safe {
writeln!(file, " - {}", cmd).unwrap();
Expand Down
10 changes: 10 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ pub fn build_registry() -> CommandRegistry {
|arg, ctx, _msg| crate::commands::config_settings::execute_autoaccept(&mut ctx.output, arg),
);

reg.register_sync_with_usage(
"/autocompact",
"Show or toggle the auto_compact tool (on/off). When off, the model cannot request conversation compaction.",
"/autocompact [on|off]",
|arg, ctx, _msg| {
crate::commands::config_settings::execute_autocompact(&mut ctx.output, arg)
},
);

// ── Per-project settings ──────────────────────────────────────────────

reg.register_sync_with_usage(
Expand Down Expand Up @@ -434,6 +443,7 @@ pub fn build_registry() -> CommandRegistry {
reg.register_subcommands("/mode", vec!["agent", "casual", "planning", "research"]);
reg.register_subcommands("/settings", vec!["all"]);
reg.register_subcommands("/autoaccept", vec!["off", "safe", "all"]);
reg.register_subcommands("/autocompact", vec!["off", "on"]);
reg.register_subcommands("/apikey", vec!["clear"]);
reg.register_subcommands("/showthink", vec!["off", "on"]);
reg.register_subcommands("/think", vec!["high", "low", "medium", "off"]);
Expand Down
15 changes: 14 additions & 1 deletion src/commands/project_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ fn execute_show(out: &mut Output) {
);
}

// ── Auto-compact ──
let ac_val = if merged.auto_compact_enabled {
"on"
} else {
"off"
};
let (ac_str, ac_src) = format_setting(ac_val, merged.auto_compact_enabled_source, None);
let _ = writeln!(out, "{BOLD}│{RESET} Auto-Compact: {ac_str} {ac_src}");

let _ = writeln!(
out,
"{BOLD}╰─────────────────────────────────────────────────╯{RESET}",
Expand Down Expand Up @@ -193,7 +202,11 @@ fn execute_init(out: &mut Output) {

// Preferred agent mode for this project.
// Valid modes: casual, planning, agent, research
// "preferred_mode": "agent"
// "preferred_mode": "agent",

// Enable or disable the auto_compact tool for this project.
// When false, the model will not see auto_compact as an available tool.
// "auto_compact_enabled": true
}
"#;

Expand Down
10 changes: 10 additions & 0 deletions src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ fn execute_summary(out: &mut Output, settings: &tinyharness_lib::config::Setting
"{BOLD}│{RESET} Auto-Accept: {auto_accept_color}{auto_accept_str}{RESET}",
);

let (ac_str, ac_color) = if settings.auto_compact_enabled {
("on", GREEN)
} else {
("off", ORANGE)
};
let _ = writeln!(
out,
"{BOLD}│{RESET} Auto-Compact: {ac_color}{ac_str}{RESET}",
);

let safe_commands = settings.get_safe_commands();
let denied_commands = settings.get_denied_commands();
let _ = writeln!(
Expand Down
25 changes: 25 additions & 0 deletions tinyharness-lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ pub struct ProjectSettings {
/// Override the preferred mode for this project
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preferred_mode: Option<AgentMode>,
/// Override auto-compact enabled setting for this project
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_compact_enabled: Option<bool>,
}

/// Discover and load `.tinyharness/config.json` by walking up from CWD.
Expand Down Expand Up @@ -198,6 +201,8 @@ pub struct MergedSettings {
pub project_md_files_source: SettingSource,
pub preferred_mode: AgentMode,
pub preferred_mode_source: SettingSource,
pub auto_compact_enabled: bool,
pub auto_compact_enabled_source: SettingSource,
}

/// Load and merge global + project settings.
Expand Down Expand Up @@ -243,6 +248,8 @@ fn merge_settings(global: &Settings, project: Option<&ProjectSettings>) -> Merge
project_md_files_source: SettingSource::Default,
preferred_mode: global.preferred_mode,
preferred_mode_source: SettingSource::Default,
auto_compact_enabled: global.auto_compact_enabled,
auto_compact_enabled_source: SettingSource::Default,
Comment thread
PTFOPlayer marked this conversation as resolved.
},
Some(p) => {
// Safe commands: project extends global
Expand Down Expand Up @@ -292,6 +299,11 @@ fn merge_settings(global: &Settings, project: Option<&ProjectSettings>) -> Merge
.map(|m| (m, SettingSource::Project))
.unwrap_or((global.preferred_mode, SettingSource::Default));

let (auto_compact_enabled, ac_source) = p
.auto_compact_enabled
.map(|v| (v, SettingSource::Project))
.unwrap_or((global.auto_compact_enabled, SettingSource::Default));
Comment thread
PTFOPlayer marked this conversation as resolved.

MergedSettings {
safe_commands,
safe_commands_source: safe_source,
Expand All @@ -305,6 +317,8 @@ fn merge_settings(global: &Settings, project: Option<&ProjectSettings>) -> Merge
project_md_files_source: md_source,
preferred_mode,
preferred_mode_source: mode_source,
auto_compact_enabled,
auto_compact_enabled_source: ac_source,
}
}
}
Expand All @@ -321,6 +335,7 @@ pub fn generate_project_config_template(settings: &Settings) -> ProjectSettings
context_limit: settings.context_limit,
project_md_files: None, // user must fill this in
preferred_mode: Some(settings.preferred_mode),
auto_compact_enabled: Some(settings.auto_compact_enabled),
}
}

Expand Down Expand Up @@ -489,6 +504,15 @@ pub struct Settings {
/// Use `TINYHARNESS_MD_FILES` env var for the highest priority override.
/// (default: None → use hardcoded defaults)
pub project_md_files: Option<Vec<String>>,
/// Enable auto-compact tool (default: true).
/// When false, the auto_compact tool is not registered with the provider,
/// so the model cannot request conversation compaction.
#[serde(default = "default_true")]
pub auto_compact_enabled: bool,
}

fn default_true() -> bool {
true
}

impl Default for Settings {
Expand All @@ -513,6 +537,7 @@ impl Default for Settings {
safe_command_prefixes: None,
denied_command_prefixes: None,
project_md_files: None,
auto_compact_enabled: true,
}
}
}
Expand Down
31 changes: 26 additions & 5 deletions tinyharness-lib/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,49 @@ impl ToolManager {
}

/// Returns the tool definitions appropriate for the given agent mode.
pub fn tools_for_mode(&self, mode: AgentMode) -> Vec<ToolDefinition> {
/// When `auto_compact_enabled` is false, the `auto_compact` tool is excluded.
pub fn tools_for_mode(
&self,
mode: AgentMode,
auto_compact_enabled: bool,
) -> Vec<ToolDefinition> {
let filter_compact = |t: &&Tool| {
if t.name == "auto_compact" {
auto_compact_enabled
} else {
true
}
};
match mode {
AgentMode::Agent => self.get_all_tool_definitions(),
AgentMode::Agent => self
.tools
.iter()
.filter(filter_compact)
.map(|t| t.to_definition())
.collect(),
AgentMode::Casual => self
.tools
.iter()
.filter(|t| t.name == "web_search" || t.name == "web_fetch")
.filter(|t| filter_compact(t) && (t.name == "web_search" || t.name == "web_fetch"))
.map(|t| t.to_definition())
.collect(),
AgentMode::Planning => self
.tools
.iter()
.filter(|t| {
t.category == ToolCategory::ReadOnly || t.category == ToolCategory::Signal
filter_compact(t)
&& (t.category == ToolCategory::ReadOnly
|| t.category == ToolCategory::Signal)
})
.map(|t| t.to_definition())
.collect(),
AgentMode::Research => self
.tools
.iter()
.filter(|t| {
t.category == ToolCategory::ReadOnly || t.category == ToolCategory::Signal
filter_compact(t)
&& (t.category == ToolCategory::ReadOnly
|| t.category == ToolCategory::Signal)
})
.map(|t| t.to_definition())
.collect(),
Expand Down
Loading