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;
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)
})
}

View File

@@ -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<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 {
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(())
}