mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-14 23:22:51 +00:00
Add sandbox details and hyperlink to progress output
Show sandbox name, CPU, and memory on a second indented detail line beneath the "Sandbox: provider" line. Hyperlink the provider name to its dashboard URL using OSC 8 terminal escape sequences (TTY only). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bde59168c9
commit
990b2e9124
7 changed files with 80 additions and 3 deletions
|
|
@ -398,6 +398,10 @@ impl Sandbox for DockerSandbox {
|
|||
self.emit(SandboxEvent::Ready {
|
||||
provider: "docker".into(),
|
||||
duration_ms: init_duration,
|
||||
name: None,
|
||||
cpu: None,
|
||||
memory: None,
|
||||
url: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -379,6 +379,10 @@ impl Sandbox for LocalSandbox {
|
|||
Ok(()) => self.emit(SandboxEvent::Ready {
|
||||
provider: "local".into(),
|
||||
duration_ms,
|
||||
name: None,
|
||||
cpu: None,
|
||||
memory: None,
|
||||
url: None,
|
||||
}),
|
||||
Err(e) => self.emit(SandboxEvent::InitializeFailed {
|
||||
provider: "local".into(),
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ pub enum SandboxEvent {
|
|||
Ready {
|
||||
provider: String,
|
||||
duration_ms: u64,
|
||||
name: Option<String>,
|
||||
cpu: Option<f64>,
|
||||
memory: Option<f64>,
|
||||
url: Option<String>,
|
||||
},
|
||||
InitializeFailed {
|
||||
provider: String,
|
||||
|
|
@ -170,6 +174,7 @@ impl SandboxEvent {
|
|||
Self::Ready {
|
||||
provider,
|
||||
duration_ms,
|
||||
..
|
||||
} => {
|
||||
info!(provider, duration_ms, "Sandbox ready");
|
||||
}
|
||||
|
|
@ -405,6 +410,10 @@ mod tests {
|
|||
SandboxEvent::Ready {
|
||||
provider: "local".into(),
|
||||
duration_ms: 50,
|
||||
name: None,
|
||||
cpu: None,
|
||||
memory: None,
|
||||
url: None,
|
||||
},
|
||||
SandboxEvent::InitializeFailed {
|
||||
provider: "docker".into(),
|
||||
|
|
|
|||
|
|
@ -189,6 +189,10 @@ impl Sandbox for MockSandbox {
|
|||
self.emit(crate::sandbox::SandboxEvent::Ready {
|
||||
provider: "mock".into(),
|
||||
duration_ms: 0,
|
||||
name: None,
|
||||
cpu: None,
|
||||
memory: None,
|
||||
url: None,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,7 +495,14 @@ pub fn format_event_summary(event: &WorkflowRunEvent, styles: &Styles) -> String
|
|||
use arc_agent::SandboxEvent;
|
||||
match event {
|
||||
SandboxEvent::Initializing { provider } => format!("[SANDBOX_INITIALIZING] provider={provider}"),
|
||||
SandboxEvent::Ready { provider, duration_ms } => format!("[SANDBOX_READY] provider={provider} duration={duration_ms}ms"),
|
||||
SandboxEvent::Ready { provider, duration_ms, name, cpu, memory, .. } => {
|
||||
let mut s = format!("[SANDBOX_READY] provider={provider}");
|
||||
if let Some(n) = name { s.push_str(&format!(" name={n}")); }
|
||||
if let Some(c) = cpu { s.push_str(&format!(" cpu={c}")); }
|
||||
if let Some(m) = memory { s.push_str(&format!(" memory={m}")); }
|
||||
s.push_str(&format!(" duration={duration_ms}ms"));
|
||||
s
|
||||
}
|
||||
SandboxEvent::InitializeFailed { provider, error, duration_ms } => format!("[SANDBOX_INIT_FAILED] provider={provider} error=\"{error}\" duration={duration_ms}ms"),
|
||||
SandboxEvent::CleanupStarted { provider } => format!("[SANDBOX_CLEANUP_STARTED] provider={provider}"),
|
||||
SandboxEvent::CleanupCompleted { provider, duration_ms } => format!("[SANDBOX_CLEANUP_COMPLETED] provider={provider} duration={duration_ms}ms"),
|
||||
|
|
@ -1451,6 +1458,10 @@ mod tests {
|
|||
event: arc_agent::SandboxEvent::Ready {
|
||||
provider: "docker".into(),
|
||||
duration_ms: 1500,
|
||||
name: None,
|
||||
cpu: None,
|
||||
memory: None,
|
||||
url: None,
|
||||
},
|
||||
};
|
||||
let s = format_event_summary(&event, test_styles());
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ cached_style!(
|
|||
);
|
||||
cached_style!(style_tool_done, " {wide_msg} {prefix:.dim}");
|
||||
cached_style!(style_static_dim, " {wide_msg:.dim}");
|
||||
cached_style!(style_sandbox_detail, " {wide_msg:.dim}");
|
||||
cached_style!(style_empty, " ");
|
||||
|
||||
// ── Cached glyphs ───────────────────────────────────────────────────────
|
||||
|
|
@ -74,6 +75,20 @@ fn format_duration_ms(ms: u64) -> String {
|
|||
format_duration_short(Duration::from_millis(ms))
|
||||
}
|
||||
|
||||
/// Wrap `text` in an OSC 8 terminal hyperlink pointing to `url`.
|
||||
fn terminal_hyperlink(url: &str, text: &str) -> String {
|
||||
format!("\x1b]8;;{url}\x1b\\{text}\x1b]8;;\x1b\\")
|
||||
}
|
||||
|
||||
/// Format a number as an integer if whole, one decimal otherwise.
|
||||
fn format_number(n: f64) -> String {
|
||||
if (n - n.round()).abs() < f64::EPSILON {
|
||||
format!("{}", n as i64)
|
||||
} else {
|
||||
format!("{n:.1}")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tool call display name ──────────────────────────────────────────────
|
||||
|
||||
fn truncate(s: &str, max: usize) -> String {
|
||||
|
|
@ -290,18 +305,41 @@ impl ProgressUI {
|
|||
SandboxEvent::Ready {
|
||||
provider,
|
||||
duration_ms,
|
||||
name,
|
||||
cpu,
|
||||
memory,
|
||||
url,
|
||||
} => {
|
||||
let dur = format_duration_ms(*duration_ms);
|
||||
let detail = match (name, cpu, memory) {
|
||||
(Some(n), Some(c), Some(m)) => {
|
||||
Some(format!("{n} ({} cpu, {} GB)", format_number(*c), format_number(*m)))
|
||||
}
|
||||
(Some(n), _, _) => Some(n.clone()),
|
||||
_ => None,
|
||||
};
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(_) => {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
let display_provider = match url {
|
||||
Some(u) => terminal_hyperlink(u, provider),
|
||||
None => provider.clone(),
|
||||
};
|
||||
if let Some(bar) = self.sandbox_bar.take() {
|
||||
bar.set_style(style_header_done());
|
||||
bar.set_prefix(dur);
|
||||
bar.finish_with_message(format!("Sandbox: {provider}"));
|
||||
bar.finish_with_message(format!("Sandbox: {display_provider}"));
|
||||
}
|
||||
if let Some(detail_str) = &detail {
|
||||
let detail_bar = tty.multi.add(ProgressBar::new_spinner());
|
||||
detail_bar.set_style(style_sandbox_detail());
|
||||
detail_bar.finish_with_message(detail_str.clone());
|
||||
}
|
||||
}
|
||||
ProgressRenderer::Plain => {
|
||||
eprintln!(" Sandbox: {provider} (ready in {dur})");
|
||||
if let Some(detail_str) = &detail {
|
||||
eprintln!(" {detail_str}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,6 +458,9 @@ impl Sandbox for DaytonaSandbox {
|
|||
}
|
||||
}
|
||||
|
||||
let sandbox_name = sandbox.name.clone();
|
||||
let sandbox_cpu = sandbox.cpu;
|
||||
let sandbox_memory = sandbox.memory;
|
||||
self.sandbox
|
||||
.set(sandbox)
|
||||
.map_err(|_| "Daytona sandbox already initialized".to_string())?;
|
||||
|
|
@ -467,6 +470,10 @@ impl Sandbox for DaytonaSandbox {
|
|||
self.emit(SandboxEvent::Ready {
|
||||
provider: "daytona".into(),
|
||||
duration_ms: init_duration,
|
||||
name: Some(sandbox_name),
|
||||
cpu: Some(sandbox_cpu),
|
||||
memory: Some(sandbox_memory),
|
||||
url: Some("https://app.daytona.io/dashboard/sandboxes".into()),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue