From 28ab8a3af47cd56e9ba1ca21ee7ed6348b386d17 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sun, 23 Aug 2026 20:24:26 +0200 Subject: [PATCH] fix(grid-agent): keep TUI terminal output isolated --- .../metacrate-grid-agent/src/interaction.rs | 27 +---- crates/metacrate-grid-agent/src/main.rs | 112 ++++++++++++++---- 2 files changed, 95 insertions(+), 44 deletions(-) diff --git a/crates/metacrate-grid-agent/src/interaction.rs b/crates/metacrate-grid-agent/src/interaction.rs index 38eee54..62baeff 100644 --- a/crates/metacrate-grid-agent/src/interaction.rs +++ b/crates/metacrate-grid-agent/src/interaction.rs @@ -1892,9 +1892,7 @@ impl AutonomousApprovalReviewer { ) .await; cancellation_bridge.abort(); - let message = result.map_err(|error| { - eprintln!("Mentra approval review failed: {error}"); - })?; + let message = result.map_err(|_| ())?; let text = message.text(); let verdict = text .trim() @@ -1904,10 +1902,6 @@ impl AutonomousApprovalReviewer { } else if verdict.eq_ignore_ascii_case("DENY") { Ok(false) } else { - eprintln!( - "Mentra approval review returned an invalid verdict ({} bytes)", - text.len() - ); Err(()) } } @@ -1976,18 +1970,8 @@ impl mentra::tool::ToolExecutor for MentraGridTool { .await { crate::tool_loop::ToolExecution::Completed(result) => Ok(result.into_inner()), - crate::tool_loop::ToolExecution::Rejected(reason) => { - eprintln!( - "Mentra grid tool rejected: name={} reason={}", - self.definition.name.as_str(), - reason.as_str() - ); - Err(reason.into_inner()) - } - crate::tool_loop::ToolExecution::Failed(reason) => { - eprintln!("Mentra grid tool failed: {}", reason.as_str()); - Err(reason.into_inner()) - } + crate::tool_loop::ToolExecution::Rejected(reason) + | crate::tool_loop::ToolExecution::Failed(reason) => Err(reason.into_inner()), crate::tool_loop::ToolExecution::AmbiguousMutation => { active.mentra_cancellation.cancel(); Err("mutation outcome is ambiguous; stopped without retry".to_owned()) @@ -2383,10 +2367,7 @@ impl InteractionResponder for PolicyLlmResponder { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .remove(&agent_id); - let message = result.map_err(|error| { - eprintln!("Mentra inference failed: {error}"); - mentra_interaction_error(&error) - })?; + let message = result.map_err(|error| mentra_interaction_error(&error))?; mentra_visible_text(&message) }) } diff --git a/crates/metacrate-grid-agent/src/main.rs b/crates/metacrate-grid-agent/src/main.rs index ae6d2ba..d59b4f0 100644 --- a/crates/metacrate-grid-agent/src/main.rs +++ b/crates/metacrate-grid-agent/src/main.rs @@ -22,6 +22,58 @@ impl fmt::Display for CliError { impl Error for CliError {} +#[cfg(feature = "live-grid")] +struct SilentLogger; + +#[cfg(feature = "live-grid")] +impl libremetaverse::logging::ILogger for SilentLogger { + fn is_enabled(&self, _level: libremetaverse::logging::LogLevel) -> bool { + false + } + fn log( + &self, + _level: libremetaverse::logging::LogLevel, + _message: &libremetaverse_types::compat::Object, + _exception: Option<&libremetaverse_types::compat::ExternalError>, + _client_name: Option<&str>, + ) { + } + fn begin_scope( + &self, + _state: libremetaverse_types::compat::Object, + ) -> Box { + Box::new(SilentScope) + } +} + +#[cfg(feature = "live-grid")] +struct SilentScope; + +#[cfg(feature = "live-grid")] +impl libremetaverse_types::compat::Close for SilentScope { + fn close(&mut self) -> Result<(), libremetaverse_types::compat::ExternalError> { + Ok(()) + } +} + +#[cfg(feature = "live-grid")] +struct SilentLoggerFactory; + +#[cfg(feature = "live-grid")] +impl libremetaverse::logging::ILoggerFactory for SilentLoggerFactory { + fn create_logger(&self, _name: &str) -> Arc { + Arc::new(SilentLogger) + } + fn shutdown(&self) -> Result<(), libremetaverse_types::compat::ExternalError> { + Ok(()) + } +} + +#[cfg(feature = "live-grid")] +fn install_silent_grid_logger() -> Result<(), libremetaverse::Error> { + libremetaverse::Logger::set_logger_factory(Box::new(SilentLoggerFactory), "tui".into()) +} + fn color_disabled() -> bool { std::env::var_os("NO_COLOR").is_some() || std::env::var_os("TERM").is_some_and(|value| value == "dumb") @@ -278,6 +330,10 @@ async fn run_live( SessionSupervisor, TcpControlConfig, TcpControlServer, WorldSnapshotSource, }; + if tui { + install_silent_grid_logger()?; + } + let connection = config.grid.clone().ok_or_else(|| { CliError("validated live configuration did not contain a grid connection".into()) })?; @@ -335,6 +391,7 @@ async fn run_live( .timeouts .startup .saturating_sub(Duration::from_secs(5)), + !tui, ) .await?; } @@ -472,7 +529,9 @@ async fn run_live( None }; - println!("grid agent session supervisor started; press Ctrl-C to stop"); + if !tui { + println!("grid agent session supervisor started; press Ctrl-C to stop"); + } let mut signal_error = None; let mut control_failure = None; let mut active_interactions = 0_usize; @@ -496,7 +555,9 @@ async fn run_live( &framework_readiness, status.generation, config.timeouts.startup.saturating_sub(Duration::from_secs(5)), + !tui, ).await + && !tui { eprintln!("framework initialization failed: {error}"); } @@ -524,14 +585,14 @@ async fn run_live( component: "session".into(), state: status.state.as_str().into(), }); - println!( - "grid session state={} generation={} transport_connected={} agent_ready={} reason={reason:?} retry_in={retry_in:?}", - status.state.as_str(), - status.generation, - status.transport_connected, - status.agent_ready, - ); - if status.agent_ready { + if !tui { + println!( + "grid session state={} generation={} transport_connected={} agent_ready={} reason={reason:?} retry_in={retry_in:?}", + status.state.as_str(), status.generation, + status.transport_connected, status.agent_ready, + ); + } + if status.agent_ready && !tui { print_ready(&live, status.generation); } } @@ -551,12 +612,12 @@ async fn run_live( pause.conversation = active_interactions != 0; }); record_interaction_observation(&live.observability, &event); - println!("grid interaction event={event:?}"); + if !tui { println!("grid interaction event={event:?}"); } } event = live.perception_observations.recv() => { let Some(event) = event else { break; }; record_perception_observation(&live.observability, &event); - println!("grid perception event={event:?}"); + if !tui { println!("grid perception event={event:?}"); } } event = live.behavior.next_observation() => { let Some(event) = event else { break; }; @@ -568,7 +629,7 @@ async fn run_live( state: format!("{to:?}").to_ascii_lowercase(), }); } - println!("grid behavior event={event:?}"); + if !tui { println!("grid behavior event={event:?}"); } } command = control_commands.recv() => { let Some(command) = command else { break; }; @@ -639,7 +700,9 @@ async fn run_live( if let Some(error) = control_failure { return Err(CliError(format!("control command failed: {error}")).into()); } - println!("grid agent stopped cleanly"); + if !tui { + println!("grid agent stopped cleanly"); + } Ok(()) } @@ -649,8 +712,13 @@ async fn prepare_live_framework( readiness: &metacrate_grid_agent::FrameworkReadiness, generation: u64, timeout: Duration, + print_status: bool, ) -> Result<(), CliError> { - println!("INITIALIZING generation={generation} waiting_for=world_state,viewport target_fps=10"); + if print_status { + println!( + "INITIALIZING generation={generation} waiting_for=world_state,viewport target_fps=10" + ); + } tokio::time::timeout(timeout, async { loop { let world_ready = live @@ -689,13 +757,15 @@ async fn prepare_live_framework( .map_err(|_| CliError("timed out waiting for stable world state and viewport".into()))?; readiness.mark_ready(generation); let stats = live.vision.viewport_stats().unwrap_or_default(); - println!( - "INITIALIZED generation={} viewport_frames={} last_render_ms={} warmup_missed_deadlines={} world_state=stable viewport_state=stable", - generation, - stats.completed_frames, - stats.last_render.as_millis(), - stats.missed_frame_deadlines, - ); + if print_status { + println!( + "INITIALIZED generation={} viewport_frames={} last_render_ms={} warmup_missed_deadlines={} world_state=stable viewport_state=stable", + generation, + stats.completed_frames, + stats.last_render.as_millis(), + stats.missed_frame_deadlines, + ); + } Ok(()) }