fix(grid-agent): keep TUI terminal output isolated
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled

This commit is contained in:
2026-08-23 20:24:26 +02:00
parent 096911a156
commit 28ab8a3af4
2 changed files with 95 additions and 44 deletions

View File

@@ -1892,9 +1892,7 @@ impl AutonomousApprovalReviewer {
) )
.await; .await;
cancellation_bridge.abort(); cancellation_bridge.abort();
let message = result.map_err(|error| { let message = result.map_err(|_| ())?;
eprintln!("Mentra approval review failed: {error}");
})?;
let text = message.text(); let text = message.text();
let verdict = text let verdict = text
.trim() .trim()
@@ -1904,10 +1902,6 @@ impl AutonomousApprovalReviewer {
} else if verdict.eq_ignore_ascii_case("DENY") { } else if verdict.eq_ignore_ascii_case("DENY") {
Ok(false) Ok(false)
} else { } else {
eprintln!(
"Mentra approval review returned an invalid verdict ({} bytes)",
text.len()
);
Err(()) Err(())
} }
} }
@@ -1976,18 +1970,8 @@ impl mentra::tool::ToolExecutor for MentraGridTool {
.await .await
{ {
crate::tool_loop::ToolExecution::Completed(result) => Ok(result.into_inner()), crate::tool_loop::ToolExecution::Completed(result) => Ok(result.into_inner()),
crate::tool_loop::ToolExecution::Rejected(reason) => { crate::tool_loop::ToolExecution::Rejected(reason)
eprintln!( | crate::tool_loop::ToolExecution::Failed(reason) => Err(reason.into_inner()),
"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::AmbiguousMutation => { crate::tool_loop::ToolExecution::AmbiguousMutation => {
active.mentra_cancellation.cancel(); active.mentra_cancellation.cancel();
Err("mutation outcome is ambiguous; stopped without retry".to_owned()) Err("mutation outcome is ambiguous; stopped without retry".to_owned())
@@ -2383,10 +2367,7 @@ impl InteractionResponder for PolicyLlmResponder {
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.remove(&agent_id); .remove(&agent_id);
let message = result.map_err(|error| { let message = result.map_err(|error| mentra_interaction_error(&error))?;
eprintln!("Mentra inference failed: {error}");
mentra_interaction_error(&error)
})?;
mentra_visible_text(&message) mentra_visible_text(&message)
}) })
} }

View File

@@ -22,6 +22,58 @@ impl fmt::Display for CliError {
impl Error 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<dyn libremetaverse_types::compat::Close> {
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<dyn libremetaverse::logging::ILogger> {
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 { fn color_disabled() -> bool {
std::env::var_os("NO_COLOR").is_some() std::env::var_os("NO_COLOR").is_some()
|| std::env::var_os("TERM").is_some_and(|value| value == "dumb") || std::env::var_os("TERM").is_some_and(|value| value == "dumb")
@@ -278,6 +330,10 @@ async fn run_live(
SessionSupervisor, TcpControlConfig, TcpControlServer, WorldSnapshotSource, SessionSupervisor, TcpControlConfig, TcpControlServer, WorldSnapshotSource,
}; };
if tui {
install_silent_grid_logger()?;
}
let connection = config.grid.clone().ok_or_else(|| { let connection = config.grid.clone().ok_or_else(|| {
CliError("validated live configuration did not contain a grid connection".into()) CliError("validated live configuration did not contain a grid connection".into())
})?; })?;
@@ -335,6 +391,7 @@ async fn run_live(
.timeouts .timeouts
.startup .startup
.saturating_sub(Duration::from_secs(5)), .saturating_sub(Duration::from_secs(5)),
!tui,
) )
.await?; .await?;
} }
@@ -472,7 +529,9 @@ async fn run_live(
None 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 signal_error = None;
let mut control_failure = None; let mut control_failure = None;
let mut active_interactions = 0_usize; let mut active_interactions = 0_usize;
@@ -496,7 +555,9 @@ async fn run_live(
&framework_readiness, &framework_readiness,
status.generation, status.generation,
config.timeouts.startup.saturating_sub(Duration::from_secs(5)), config.timeouts.startup.saturating_sub(Duration::from_secs(5)),
!tui,
).await ).await
&& !tui
{ {
eprintln!("framework initialization failed: {error}"); eprintln!("framework initialization failed: {error}");
} }
@@ -524,14 +585,14 @@ async fn run_live(
component: "session".into(), component: "session".into(),
state: status.state.as_str().into(), state: status.state.as_str().into(),
}); });
println!( if !tui {
"grid session state={} generation={} transport_connected={} agent_ready={} reason={reason:?} retry_in={retry_in:?}", println!(
status.state.as_str(), "grid session state={} generation={} transport_connected={} agent_ready={} reason={reason:?} retry_in={retry_in:?}",
status.generation, status.state.as_str(), status.generation,
status.transport_connected, status.transport_connected, status.agent_ready,
status.agent_ready, );
); }
if status.agent_ready { if status.agent_ready && !tui {
print_ready(&live, status.generation); print_ready(&live, status.generation);
} }
} }
@@ -551,12 +612,12 @@ async fn run_live(
pause.conversation = active_interactions != 0; pause.conversation = active_interactions != 0;
}); });
record_interaction_observation(&live.observability, &event); record_interaction_observation(&live.observability, &event);
println!("grid interaction event={event:?}"); if !tui { println!("grid interaction event={event:?}"); }
} }
event = live.perception_observations.recv() => { event = live.perception_observations.recv() => {
let Some(event) = event else { break; }; let Some(event) = event else { break; };
record_perception_observation(&live.observability, &event); record_perception_observation(&live.observability, &event);
println!("grid perception event={event:?}"); if !tui { println!("grid perception event={event:?}"); }
} }
event = live.behavior.next_observation() => { event = live.behavior.next_observation() => {
let Some(event) = event else { break; }; let Some(event) = event else { break; };
@@ -568,7 +629,7 @@ async fn run_live(
state: format!("{to:?}").to_ascii_lowercase(), state: format!("{to:?}").to_ascii_lowercase(),
}); });
} }
println!("grid behavior event={event:?}"); if !tui { println!("grid behavior event={event:?}"); }
} }
command = control_commands.recv() => { command = control_commands.recv() => {
let Some(command) = command else { break; }; let Some(command) = command else { break; };
@@ -639,7 +700,9 @@ async fn run_live(
if let Some(error) = control_failure { if let Some(error) = control_failure {
return Err(CliError(format!("control command failed: {error}")).into()); return Err(CliError(format!("control command failed: {error}")).into());
} }
println!("grid agent stopped cleanly"); if !tui {
println!("grid agent stopped cleanly");
}
Ok(()) Ok(())
} }
@@ -649,8 +712,13 @@ async fn prepare_live_framework(
readiness: &metacrate_grid_agent::FrameworkReadiness, readiness: &metacrate_grid_agent::FrameworkReadiness,
generation: u64, generation: u64,
timeout: Duration, timeout: Duration,
print_status: bool,
) -> Result<(), CliError> { ) -> 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 { tokio::time::timeout(timeout, async {
loop { loop {
let world_ready = live 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()))?; .map_err(|_| CliError("timed out waiting for stable world state and viewport".into()))?;
readiness.mark_ready(generation); readiness.mark_ready(generation);
let stats = live.vision.viewport_stats().unwrap_or_default(); let stats = live.vision.viewport_stats().unwrap_or_default();
println!( if print_status {
"INITIALIZED generation={} viewport_frames={} last_render_ms={} warmup_missed_deadlines={} world_state=stable viewport_state=stable", println!(
generation, "INITIALIZED generation={} viewport_frames={} last_render_ms={} warmup_missed_deadlines={} world_state=stable viewport_state=stable",
stats.completed_frames, generation,
stats.last_render.as_millis(), stats.completed_frames,
stats.missed_frame_deadlines, stats.last_render.as_millis(),
); stats.missed_frame_deadlines,
);
}
Ok(()) Ok(())
} }