mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Enable additional pedantic clippy lints and fix violations
Enables cast_possible_truncation, cast_sign_loss, items_after_statements, needless_pass_by_value, return_self_not_must_use, uninlined_format_args, unreadable_literal, and unnested_or_patterns. Keeps doc_markdown disabled. Replaces unsafe `as` casts with try_from().unwrap() throughout, using #[allow] only for f64-to-integer casts which have no try_from equivalent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1dc0ebce52
commit
2f4fc23f3d
68 changed files with 256 additions and 234 deletions
|
|
@ -88,17 +88,9 @@ too_many_arguments = "allow"
|
|||
too_many_lines = "allow"
|
||||
used_underscore_binding = "allow"
|
||||
if_not_else = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
doc_markdown = "allow"
|
||||
items_after_statements = "allow"
|
||||
needless_pass_by_value = "allow"
|
||||
return_self_not_must_use = "allow"
|
||||
uninlined_format_args = "allow"
|
||||
unreadable_literal = "allow"
|
||||
unnested_or_patterns = "allow"
|
||||
# Disallowed restriction lints
|
||||
print_stdout = "warn"
|
||||
print_stderr = "warn"
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub trait AgentProfile: Send + Sync {
|
|||
fn context_window_size(&self) -> usize {
|
||||
Catalog::builtin()
|
||||
.get(self.model())
|
||||
.map(|m| m.context_window() as usize)
|
||||
.map(|m| usize::try_from(m.context_window()).unwrap())
|
||||
.unwrap_or(200_000)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,7 @@ fn tool_category(name: &str) -> &'static str {
|
|||
fn is_auto_approved(level: PermissionLevel, category: &str) -> bool {
|
||||
matches!(
|
||||
(level, category),
|
||||
(_, "read")
|
||||
| (_, "subagent")
|
||||
(_, "read" | "subagent")
|
||||
| (PermissionLevel::ReadWrite | PermissionLevel::Full, "write")
|
||||
| (PermissionLevel::Full, "shell")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use fabro_mcp::connection_manager::{McpConnectionManager, call_result_to_string}
|
|||
use crate::tool_registry::RegisteredTool;
|
||||
|
||||
/// Create `RegisteredTool` instances for every tool exposed by connected MCP servers.
|
||||
pub fn make_mcp_tools(manager: Arc<McpConnectionManager>) -> Vec<RegisteredTool> {
|
||||
pub fn make_mcp_tools(manager: &Arc<McpConnectionManager>) -> Vec<RegisteredTool> {
|
||||
manager
|
||||
.all_tools()
|
||||
.iter()
|
||||
.map(|(qualified_name, info)| {
|
||||
let mgr = Arc::clone(&manager);
|
||||
let mgr = Arc::clone(manager);
|
||||
let name = qualified_name.clone();
|
||||
let tool_timeout = std::time::Duration::from_secs(120);
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ mod tests {
|
|||
let mut mgr = McpConnectionManager::new();
|
||||
mgr.start_servers(&[config]).await;
|
||||
|
||||
let tools = make_mcp_tools(Arc::new(mgr));
|
||||
let tools = make_mcp_tools(&Arc::new(mgr));
|
||||
assert_eq!(tools.len(), 1);
|
||||
assert_eq!(tools[0].definition.name, "mcp__test_echo__echo");
|
||||
assert_eq!(tools[0].definition.description, "Echo back the message");
|
||||
|
|
@ -79,7 +79,7 @@ mod tests {
|
|||
let mut mgr = McpConnectionManager::new();
|
||||
mgr.start_servers(&[config]).await;
|
||||
|
||||
let tools = make_mcp_tools(Arc::new(mgr));
|
||||
let tools = make_mcp_tools(&Arc::new(mgr));
|
||||
let tool = &tools[0];
|
||||
|
||||
use crate::sandbox::Sandbox;
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ impl Session {
|
|||
}
|
||||
|
||||
let manager = Arc::new(manager);
|
||||
let mcp_tools = mcp_integration::make_mcp_tools(manager);
|
||||
let mcp_tools = mcp_integration::make_mcp_tools(&manager);
|
||||
if let Some(profile) = Arc::get_mut(&mut self.provider_profile) {
|
||||
for tool in mcp_tools {
|
||||
profile.tool_registry_mut().register(tool);
|
||||
|
|
@ -588,6 +588,8 @@ impl Session {
|
|||
}
|
||||
|
||||
async fn run_single_input(&mut self, input: &str) -> Result<(), AgentError> {
|
||||
const STREAM_CONSUME_RETRIES: usize = 3;
|
||||
|
||||
if self.state == SessionState::Closed {
|
||||
return Err(AgentError::SessionClosed);
|
||||
}
|
||||
|
|
@ -700,7 +702,6 @@ impl Session {
|
|||
// Consume the stream, retrying up to 3 times if the provider
|
||||
// closes the stream without sending a Finish event. If visible
|
||||
// output was already emitted, clear it before replaying the turn.
|
||||
const STREAM_CONSUME_RETRIES: usize = 3;
|
||||
let mut response = None;
|
||||
|
||||
for stream_attempt in 0..=STREAM_CONSUME_RETRIES {
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ pub fn make_spawn_agent_tool(
|
|||
let max_turns = args
|
||||
.get("max_turns")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|v| v as usize);
|
||||
.map(|v| usize::try_from(v).unwrap());
|
||||
|
||||
// Note: working_dir and model require session factory changes to wire through
|
||||
let mut session = session_factory();
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ async fn execute_and_emit_one_tool_with_lookup(
|
|||
debug!(tool = %tc.name, hook_event = "pre_tool_use", "Calling tool hook");
|
||||
let start = std::time::Instant::now();
|
||||
let decision = hooks.pre_tool_use(&tc.name, &tc.arguments).await;
|
||||
let elapsed = start.elapsed().as_millis() as u64;
|
||||
let elapsed = u64::try_from(start.elapsed().as_millis()).unwrap();
|
||||
debug!(tool = %tc.name, hook_event = "pre_tool_use", ?decision, duration_ms = elapsed, "Tool hook complete");
|
||||
|
||||
if let ToolHookDecision::Block { reason } = decision {
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ pub fn make_read_file_tool() -> RegisteredTool {
|
|||
let offset = args.get("offset").and_then(serde_json::Value::as_u64);
|
||||
let limit = args.get("limit").and_then(serde_json::Value::as_u64);
|
||||
|
||||
let offset_usize = offset.map(|v| v as usize);
|
||||
let limit_usize = limit.map(|v| v as usize);
|
||||
let offset_usize = offset.map(|v| usize::try_from(v).unwrap());
|
||||
let limit_usize = limit.map(|v| usize::try_from(v).unwrap());
|
||||
|
||||
let content = ctx
|
||||
.env
|
||||
|
|
@ -279,6 +279,10 @@ pub fn make_grep_tool() -> RegisteredTool {
|
|||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or(".");
|
||||
|
||||
let max_results = args
|
||||
.get("max_results")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|v| usize::try_from(v).unwrap());
|
||||
let options = GrepOptions {
|
||||
glob_filter: args
|
||||
.get("glob_filter")
|
||||
|
|
@ -288,10 +292,7 @@ pub fn make_grep_tool() -> RegisteredTool {
|
|||
.get("case_insensitive")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false),
|
||||
max_results: args
|
||||
.get("max_results")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|v| v as usize),
|
||||
max_results,
|
||||
};
|
||||
|
||||
let results = ctx.env.grep(pattern, path, &options).await?;
|
||||
|
|
@ -402,7 +403,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool {
|
|||
let depth = args
|
||||
.get("depth")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|v| v as usize);
|
||||
.map(|v| usize::try_from(v).unwrap());
|
||||
|
||||
let entries = ctx.env.list_directory(path, depth).await?;
|
||||
let lines: Vec<String> = entries
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Demo mode handlers that return static data for all API endpoints.
|
||||
//! Activated per-request via the `X-Fabro-Demo: 1` header to showcase the UI without a real backend.
|
||||
#![allow(clippy::default_trait_access)]
|
||||
#![allow(clippy::default_trait_access, clippy::unreadable_literal)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub fn decode_pem_env(name: &str, value: &str) -> String {
|
|||
///
|
||||
/// Call this once at startup before serving requests. Panics if the
|
||||
/// configuration is invalid (JWT strategy but no public key, or mTLS without TLS config).
|
||||
pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: Vec<String>) -> AuthMode {
|
||||
pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: &[String]) -> AuthMode {
|
||||
use fabro_config::server::ApiAuthStrategy;
|
||||
|
||||
if api_config.authentication_strategies.is_empty() {
|
||||
|
|
@ -93,7 +93,7 @@ pub fn resolve_auth_mode(api_config: &ApiSettings, allowed_usernames: Vec<String
|
|||
AuthStrategy::Jwt {
|
||||
key: Arc::new(key),
|
||||
validation: Arc::new(jwt_validation()),
|
||||
allowed_usernames: allowed_usernames.clone(),
|
||||
allowed_usernames: allowed_usernames.to_vec(),
|
||||
}
|
||||
}
|
||||
ApiAuthStrategy::Mtls => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
|
|||
.as_ref()
|
||||
.map(|w| w.auth.allowed_usernames.clone())
|
||||
.unwrap_or_default();
|
||||
let auth_mode = resolve_auth_mode(&api, allowed_usernames);
|
||||
let auth_mode = resolve_auth_mode(&api, &allowed_usernames);
|
||||
let client_auth = api.tls.as_ref().map(|_| client_auth_from_mode(&auth_mode));
|
||||
let max_concurrent_runs = args
|
||||
.max_concurrent_runs
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ fn turns_to_messages(turns: &[fabro_api_types::SessionTurn]) -> Vec<LlmMessage>
|
|||
|
||||
fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, seq_at_start: u64) {
|
||||
tokio::spawn(async move {
|
||||
use futures_util::StreamExt;
|
||||
let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = {
|
||||
let store = store.read().expect("session store lock poisoned");
|
||||
let Some(session) = store.get(&session_id) else {
|
||||
|
|
@ -156,7 +157,6 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
|
|||
}
|
||||
};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
let mut stream_result = stream_result;
|
||||
let mut full_text = String::new();
|
||||
while let Some(event) = stream_result.next().await {
|
||||
|
|
@ -321,6 +321,7 @@ pub async fn stream_session_events(
|
|||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Response {
|
||||
use tokio_stream::StreamExt;
|
||||
let rx = {
|
||||
let store = state.sessions.read().expect("session store lock poisoned");
|
||||
match store.get(&id) {
|
||||
|
|
@ -329,8 +330,6 @@ pub async fn stream_session_events(
|
|||
}
|
||||
};
|
||||
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
let stream = BroadcastStream::new(rx).filter_map(|result| match result {
|
||||
Ok(event) => {
|
||||
let sse: Option<Event> = match event {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use fabro_config::server::TlsSettings;
|
|||
use crate::jwt_auth::PeerCertificates;
|
||||
|
||||
/// How client certificates should be verified.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClientAuth {
|
||||
/// No client certificates requested (TLS encryption only).
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ async fn list_from(
|
|||
args: PrListArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
struct PrRow {
|
||||
run_id: String,
|
||||
number: u64,
|
||||
state: String,
|
||||
title: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
let creds = github_app.context(
|
||||
"GitHub App credentials required — set GITHUB_APP_PRIVATE_KEY and configure app_id",
|
||||
)?;
|
||||
|
|
@ -45,14 +53,6 @@ async fn list_from(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
struct PrRow {
|
||||
run_id: String,
|
||||
number: u64,
|
||||
state: String,
|
||||
title: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
let futures: Vec<_> = entries
|
||||
.iter()
|
||||
.map(|(run_id, record)| {
|
||||
|
|
|
|||
|
|
@ -99,10 +99,7 @@ fn build_live_diff_cmd(base_sha: &str, stat: bool, shortstat: bool) -> String {
|
|||
|_| format!("'{}'", base_sha.replace('\'', "'\\''")),
|
||||
|q| q.to_string(),
|
||||
);
|
||||
format!(
|
||||
"{} add -N . && {} diff{flags} {quoted_sha}",
|
||||
GIT_REMOTE, GIT_REMOTE
|
||||
)
|
||||
format!("{GIT_REMOTE} add -N . && {GIT_REMOTE} diff{flags} {quoted_sha}")
|
||||
}
|
||||
|
||||
fn colorize_diff_line(line: &str) -> String {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub(crate) fn run(args: &ForkArgs, styles: &Styles) -> Result<()> {
|
|||
.transpose()?;
|
||||
let new_run_id = fork(
|
||||
&store,
|
||||
ForkRunInput {
|
||||
&ForkRunInput {
|
||||
source_run_id: run_id.clone(),
|
||||
target,
|
||||
push: !args.no_push,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use tracing::{debug, info};
|
|||
use crate::args::LogsArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub(crate) fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: &LogsArgs, styles: &Styles) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run = resolve_run(&base, &args.run)?;
|
||||
|
|
@ -241,9 +241,10 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option<String>
|
|||
lines.push(format!(
|
||||
"{}{}",
|
||||
pad,
|
||||
styles
|
||||
.dim
|
||||
.apply_to(format!("Tokens: {}", format_tokens(total as u64)))
|
||||
styles.dim.apply_to(format!(
|
||||
"Tokens: {}",
|
||||
format_tokens(u64::try_from(total).unwrap())
|
||||
))
|
||||
));
|
||||
}
|
||||
if let Some(cache_read) = usage
|
||||
|
|
@ -259,8 +260,8 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option<String>
|
|||
pad,
|
||||
styles.dim.apply_to(format!(
|
||||
"Cache: {} read, {} write",
|
||||
format_tokens(cache_read as u64),
|
||||
format_tokens(cache_write as u64)
|
||||
format_tokens(u64::try_from(cache_read).unwrap()),
|
||||
format_tokens(u64::try_from(cache_write).unwrap())
|
||||
))
|
||||
));
|
||||
}
|
||||
|
|
@ -274,7 +275,7 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option<String>
|
|||
pad,
|
||||
styles.dim.apply_to(format!(
|
||||
"Reasoning: {} tokens",
|
||||
format_tokens(reasoning as u64)
|
||||
format_tokens(u64::try_from(reasoning).unwrap())
|
||||
))
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
|||
RunCommands::Diff(args) => diff::run(args).await,
|
||||
RunCommands::Logs(args) => {
|
||||
let styles = Styles::detect_stdout();
|
||||
logs::run(args, &styles)
|
||||
logs::run(&args, &styles)
|
||||
}
|
||||
RunCommands::Resume(args) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
|
|
@ -87,7 +87,7 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
|||
}
|
||||
RunCommands::Wait(args) => {
|
||||
let styles = Styles::detect_stderr();
|
||||
wait::run(args, &styles)
|
||||
wait::run(&args, &styles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub(crate) fn run(args: &RewindArgs, styles: &Styles) -> Result<()> {
|
|||
|
||||
rewind(
|
||||
&store,
|
||||
RewindInput {
|
||||
&RewindInput {
|
||||
run_id: run_id.clone(),
|
||||
target,
|
||||
push: !args.no_push,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub(crate) fn format_duration_short(d: Duration) -> String {
|
|||
if secs >= 60 {
|
||||
format!("{}m{:02}s", secs / 60, secs % 60)
|
||||
} else if d.as_millis() >= 1000 {
|
||||
format!("{}s", secs)
|
||||
format!("{secs}s")
|
||||
} else {
|
||||
format!("{}ms", d.as_millis())
|
||||
}
|
||||
|
|
@ -83,7 +83,9 @@ fn terminal_hyperlink(url: &str, text: &str) -> String {
|
|||
/// 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)
|
||||
#[allow(clippy::cast_possible_truncation)] // f64-to-integer: intentional rounding
|
||||
let i = n as i64;
|
||||
format!("{i}")
|
||||
} else {
|
||||
format!("{n:.1}")
|
||||
}
|
||||
|
|
@ -718,7 +720,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
"SetupStarted" => {
|
||||
let count = u64_field("command_count") as usize;
|
||||
let count = usize::try_from(u64_field("command_count")).unwrap();
|
||||
self.on_setup_started(count);
|
||||
}
|
||||
"SetupCompleted" => {
|
||||
|
|
@ -972,7 +974,7 @@ impl ProgressUI {
|
|||
}
|
||||
"DevcontainerLifecycleStarted" => {
|
||||
let phase = str_field("phase").unwrap_or("?");
|
||||
let command_count = u64_field("command_count") as usize;
|
||||
let command_count = usize::try_from(u64_field("command_count")).unwrap();
|
||||
self.devcontainer_command_count = command_count;
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
|
|
@ -1478,6 +1480,8 @@ impl ProgressUI {
|
|||
..
|
||||
} if self.verbose => {
|
||||
let yellow = Style::new().yellow();
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
// f64-to-integer: delay is non-negative and fits in u64
|
||||
let delay_ms = (*delay_secs * 1000.0) as u64;
|
||||
let dur = format_duration_ms(delay_ms);
|
||||
self.insert_info_line_for_stage(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::args::WaitArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::format_duration_ms;
|
||||
|
||||
pub(crate) fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: &WaitArgs, styles: &Styles) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run_info = resolve_run(&base, &args.run)?;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ pub(crate) fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
|
|||
None => match run.start_time_dt {
|
||||
Some(start) => {
|
||||
let elapsed = now.signed_duration_since(start);
|
||||
format_duration_ms(elapsed.num_milliseconds().max(0) as u64)
|
||||
format_duration_ms(
|
||||
u64::try_from(elapsed.num_milliseconds().max(0)).unwrap(),
|
||||
)
|
||||
}
|
||||
None => "-".to_string(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,11 +22,6 @@ pub(super) fn df_command(args: &DfArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -> Result<()> {
|
||||
let runs = scan_runs(runs_base)?;
|
||||
let mut active_count = 0u64;
|
||||
let mut total_run_size = 0u64;
|
||||
let mut reclaimable_run_size = 0u64;
|
||||
|
||||
struct RunSizeInfo {
|
||||
run_id: String,
|
||||
workflow_name: String,
|
||||
|
|
@ -35,6 +30,11 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
|
|||
size: u64,
|
||||
}
|
||||
|
||||
let runs = scan_runs(runs_base)?;
|
||||
let mut active_count = 0u64;
|
||||
let mut total_run_size = 0u64;
|
||||
let mut reclaimable_run_size = 0u64;
|
||||
|
||||
let mut run_details = Vec::new();
|
||||
for run in &runs {
|
||||
let size = dir_size(&run.path);
|
||||
|
|
@ -96,7 +96,11 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
|
|||
}
|
||||
|
||||
let run_reclaim_pct = if total_run_size > 0 {
|
||||
(reclaimable_run_size as f64 / total_run_size as f64 * 100.0) as u64
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
// f64-to-integer: percentage is 0-100
|
||||
{
|
||||
(reclaimable_run_size as f64 / total_run_size as f64 * 100.0) as u64
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ async fn main() {
|
|||
let raw_args: Vec<String> = std::env::args().collect();
|
||||
|
||||
let (command_name, result) = main_inner().await;
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap();
|
||||
|
||||
let is_error = result.is_err();
|
||||
let command = sanitize::sanitize_command(&raw_args, &command_name);
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ impl Combine for FabroConfig {
|
|||
}
|
||||
|
||||
impl FabroConfig {
|
||||
#[must_use]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
Combine::combine(self, other)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ impl Context {
|
|||
|
||||
/// Deep copy for parallel branch isolation.
|
||||
/// `.clone()` shares state (Arc clone); `.fork()` creates an independent copy.
|
||||
#[must_use]
|
||||
pub fn fork(&self) -> Self {
|
||||
Self {
|
||||
values: Arc::new(RwLock::new(self.snapshot())),
|
||||
|
|
@ -64,7 +65,8 @@ impl Context {
|
|||
pub fn node_visit_count(&self) -> usize {
|
||||
self.get("internal.node_visit_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as usize
|
||||
.map(|v| usize::try_from(v).unwrap())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,21 +51,25 @@ impl<G: Graph + 'static> ExecutorBuilder<G> {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn lifecycle(mut self, lifecycle: Box<dyn RunLifecycle<G>>) -> Self {
|
||||
self.lifecycle = Some(lifecycle);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cancel_token(mut self, token: Arc<AtomicBool>) -> Self {
|
||||
self.options.cancel_token = Some(token);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn stall_token(mut self, token: CancellationToken) -> Self {
|
||||
self.options.stall_token = Some(token);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max_node_visits(mut self, limit: usize) -> Self {
|
||||
self.options.max_node_visits = Some(limit);
|
||||
self
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub(crate) fn generate(
|
|||
}
|
||||
|
||||
if let Some(user) = remote_user {
|
||||
sections.push(format!("USER {}", user));
|
||||
sections.push(format!("USER {user}"));
|
||||
}
|
||||
|
||||
let mut result = sections.join("\n\n");
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ impl DevcontainerResolver {
|
|||
ports
|
||||
.iter()
|
||||
.filter_map(|p| match p {
|
||||
serde_json::Value::Number(n) => n.as_u64().map(|n| n as u16),
|
||||
serde_json::Value::Number(n) => n.as_u64().map(|n| u16::try_from(n).unwrap()),
|
||||
serde_json::Value::String(s) => {
|
||||
let s = s.split('/').next().unwrap_or(s); // strip protocol
|
||||
if let Some((_host, container)) = s.split_once(':') {
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@ pub enum FileMode {
|
|||
impl FileMode {
|
||||
fn as_i32(self) -> i32 {
|
||||
match self {
|
||||
Self::Blob => 0o100644,
|
||||
Self::BlobExecutable => 0o100755,
|
||||
Self::Tree => 0o040000,
|
||||
Self::Blob => 0o100_644,
|
||||
Self::BlobExecutable => 0o100_755,
|
||||
Self::Tree => 0o040_000,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_i32(mode: i32) -> Self {
|
||||
match mode {
|
||||
0o100755 => Self::BlobExecutable,
|
||||
0o040000 => Self::Tree,
|
||||
0o100_755 => Self::BlobExecutable,
|
||||
0o040_000 => Self::Tree,
|
||||
_ => Self::Blob,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub struct Trailer<'a> {
|
|||
}
|
||||
|
||||
/// Append a trailer to a commit message, inserting a blank-line separator if needed.
|
||||
pub fn append(message: &str, trailer: Trailer<'_>) -> String {
|
||||
pub fn append(message: &str, trailer: &Trailer<'_>) -> String {
|
||||
let trailer_line = format!("{}: {}", trailer.key, trailer.value);
|
||||
let trimmed = message.trim_end();
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ mod tests {
|
|||
fn append_to_simple_message() {
|
||||
let result = append(
|
||||
"Initial commit",
|
||||
Trailer {
|
||||
&Trailer {
|
||||
key: "My-Checkpoint",
|
||||
value: "abc123",
|
||||
},
|
||||
|
|
@ -106,7 +106,7 @@ mod tests {
|
|||
let msg = "Initial commit\n\nSigned-off-by: Alice <alice@example.com>\n";
|
||||
let result = append(
|
||||
msg,
|
||||
Trailer {
|
||||
&Trailer {
|
||||
key: "My-Checkpoint",
|
||||
value: "abc123",
|
||||
},
|
||||
|
|
@ -122,7 +122,7 @@ mod tests {
|
|||
let msg = "Initial commit\n\nThis is a longer description of the change.\n";
|
||||
let result = append(
|
||||
msg,
|
||||
Trailer {
|
||||
&Trailer {
|
||||
key: "My-Checkpoint",
|
||||
value: "abc123",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -290,6 +290,13 @@ pub async fn create_pull_request(
|
|||
body: &str,
|
||||
draft: bool,
|
||||
) -> Result<CreatedPullRequest, String> {
|
||||
#[derive(Deserialize)]
|
||||
struct PullRequestResponse {
|
||||
html_url: String,
|
||||
number: u64,
|
||||
node_id: String,
|
||||
}
|
||||
|
||||
let jwt = sign_app_jwt(&creds.app_id, &creds.private_key_pem)?;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
|
|
@ -329,8 +336,7 @@ pub async fn create_pull_request(
|
|||
}
|
||||
401 | 403 => {
|
||||
return Err(format!(
|
||||
"Authentication failed creating pull request ({})",
|
||||
status
|
||||
"Authentication failed creating pull request ({status})"
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -341,13 +347,6 @@ pub async fn create_pull_request(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PullRequestResponse {
|
||||
html_url: String,
|
||||
number: u64,
|
||||
node_id: String,
|
||||
}
|
||||
|
||||
let pr: PullRequestResponse = resp
|
||||
.json()
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ impl HookExecutorImpl {
|
|||
work_dir: Option<&Path>,
|
||||
) -> HookDecision {
|
||||
let context_json = serde_json::to_string(context).unwrap_or_default();
|
||||
let timeout_ms = definition.timeout().as_millis() as u64;
|
||||
let timeout_ms = u64::try_from(definition.timeout().as_millis()).unwrap();
|
||||
|
||||
let mut env_vars = HashMap::new();
|
||||
env_vars.insert("FABRO_EVENT".to_string(), context.event.to_string());
|
||||
|
|
@ -589,7 +589,7 @@ impl HookExecutor for HookExecutorImpl {
|
|||
},
|
||||
};
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap();
|
||||
HookResult {
|
||||
hook_name: definition.name.clone(),
|
||||
decision,
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ impl HookDecision {
|
|||
match (&self, &other) {
|
||||
(Self::Block { .. }, _) => self,
|
||||
(_, Self::Block { .. }) => other,
|
||||
(Self::Skip { .. }, _) | (Self::Override { .. }, _) => self,
|
||||
(_, Self::Skip { .. }) | (_, Self::Override { .. }) => other,
|
||||
(Self::Skip { .. } | Self::Override { .. }, _) => self,
|
||||
(_, Self::Skip { .. } | Self::Override { .. }) => other,
|
||||
_ => Self::Proceed,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ fn format_cost(cost: Option<f64>) -> String {
|
|||
fn format_speed(tps: Option<f64>) -> String {
|
||||
match tps {
|
||||
None => "-".to_string(),
|
||||
#[allow(clippy::cast_possible_truncation)] // f64-to-integer: fractional loss is fine
|
||||
Some(t) => format!("{} tok/s", t as i64),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ impl McpClient {
|
|||
let mut header_map = HeaderMap::new();
|
||||
for (key, value) in headers {
|
||||
let name = HeaderName::from_bytes(key.as_bytes())
|
||||
.map_err(|e| anyhow!("invalid header name '{}': {}", key, e))?;
|
||||
.map_err(|e| anyhow!("invalid header name '{key}': {e}"))?;
|
||||
let val = HeaderValue::from_str(value)
|
||||
.map_err(|e| anyhow!("invalid header value for '{}': {}", key, e))?;
|
||||
.map_err(|e| anyhow!("invalid header value for '{key}': {e}"))?;
|
||||
header_map.insert(name, val);
|
||||
}
|
||||
builder = builder.default_headers(header_map);
|
||||
|
|
@ -207,8 +207,7 @@ impl McpClient {
|
|||
serde_json::Value::Null => None,
|
||||
other => {
|
||||
return Err(anyhow!(
|
||||
"MCP tool arguments must be a JSON object, got {}",
|
||||
other
|
||||
"MCP tool arguments must be a JSON object, got {other}"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -906,7 +906,7 @@ impl Sandbox for DaytonaSandbox {
|
|||
name: f.name,
|
||||
is_dir: f.is_dir,
|
||||
size: if f.size > 0 {
|
||||
Some(f.size as u64)
|
||||
Some(u64::try_from(f.size).unwrap())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
|
@ -937,7 +937,7 @@ impl Sandbox for DaytonaSandbox {
|
|||
.map_err(|e| format!("Failed to get process service: {e}"))?;
|
||||
|
||||
tracing::info!(
|
||||
elapsed_ms = start.elapsed().as_millis() as u64,
|
||||
elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
"exec_command: process service acquired, starting select"
|
||||
);
|
||||
|
||||
|
|
@ -976,7 +976,7 @@ impl Sandbox for DaytonaSandbox {
|
|||
let result = tokio::select! {
|
||||
res = exec_future => {
|
||||
tracing::info!(
|
||||
elapsed_ms = start.elapsed().as_millis() as u64,
|
||||
elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
ok = res.is_ok(),
|
||||
"exec_command: HTTP response received"
|
||||
);
|
||||
|
|
@ -984,7 +984,7 @@ impl Sandbox for DaytonaSandbox {
|
|||
}
|
||||
() = time::sleep(timeout_duration) => {
|
||||
tracing::info!(
|
||||
elapsed_ms = start.elapsed().as_millis() as u64,
|
||||
elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
timeout_ms,
|
||||
"exec_command: client-side timeout fired"
|
||||
);
|
||||
|
|
@ -993,12 +993,12 @@ impl Sandbox for DaytonaSandbox {
|
|||
stderr: "Command timed out locally".to_string(),
|
||||
exit_code: -1,
|
||||
timed_out: true,
|
||||
duration_ms: start.elapsed().as_millis() as u64,
|
||||
duration_ms: u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
});
|
||||
}
|
||||
() = token.cancelled() => {
|
||||
tracing::info!(
|
||||
elapsed_ms = start.elapsed().as_millis() as u64,
|
||||
elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
"exec_command: cancelled via token"
|
||||
);
|
||||
return Ok(ExecResult {
|
||||
|
|
@ -1006,12 +1006,12 @@ impl Sandbox for DaytonaSandbox {
|
|||
stderr: "Command cancelled".to_string(),
|
||||
exit_code: -1,
|
||||
timed_out: true,
|
||||
duration_ms: start.elapsed().as_millis() as u64,
|
||||
duration_ms: u64::try_from(start.elapsed().as_millis()).unwrap(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap();
|
||||
|
||||
// The Daytona SDK returns combined output in `result` field.
|
||||
// Separate stderr isn't available in the simple execute_command API.
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ impl DockerSandbox {
|
|||
.await
|
||||
.map_err(|e| format!("Failed to inspect exec: {e}"))?;
|
||||
|
||||
let exit_code = inspect.exit_code.unwrap_or(-1) as i32;
|
||||
let exit_code = i32::try_from(inspect.exit_code.unwrap_or(-1)).unwrap();
|
||||
Ok((stdout, stderr, exit_code))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,9 +118,6 @@ impl Sandbox for LocalSandbox {
|
|||
path: &str,
|
||||
depth: Option<usize>,
|
||||
) -> Result<Vec<DirEntry>, String> {
|
||||
let full_path = self.resolve_path(path);
|
||||
let max_depth = depth.unwrap_or(1);
|
||||
|
||||
fn list_recursive(
|
||||
base: &std::path::Path,
|
||||
prefix: &str,
|
||||
|
|
@ -160,6 +157,8 @@ impl Sandbox for LocalSandbox {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
let full_path = self.resolve_path(path);
|
||||
let max_depth = depth.unwrap_or(1);
|
||||
let mut entries = Vec::new();
|
||||
list_recursive(&full_path, "", 0, max_depth, &mut entries)?;
|
||||
Ok(entries)
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ impl RunStore for SlateRunStore {
|
|||
let mut iter = self.inner.db.scan_prefix(prefix.as_bytes()).await?;
|
||||
let mut visits = BTreeSet::new();
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
if let Some((current_node_id, visit, _)) = keys::parse_node_key(&key) {
|
||||
if current_node_id == node_id {
|
||||
visits.insert(visit);
|
||||
|
|
@ -400,7 +400,7 @@ impl RunStore for SlateRunStore {
|
|||
let mut iter = self.inner.db.scan_prefix(prefix.as_bytes()).await?;
|
||||
let mut assets = Vec::new();
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
if let Some(asset) = key.strip_prefix(&prefix) {
|
||||
assets.push(asset.to_string());
|
||||
}
|
||||
|
|
@ -417,7 +417,7 @@ impl RunStore for SlateRunStore {
|
|||
let mut iter = self.inner.db.scan_prefix(b"nodes/").await?;
|
||||
let mut visits = BTreeSet::new();
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
if let Some((node_id, visit, _)) = keys::parse_node_key(&key) {
|
||||
visits.insert((node_id, visit));
|
||||
}
|
||||
|
|
@ -497,7 +497,7 @@ where
|
|||
let mut iter = db.scan_prefix(prefix.as_bytes()).await?;
|
||||
let mut max_seq = 0;
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
if let Some(seq) = parse(&key) {
|
||||
max_seq = max_seq.max(seq);
|
||||
}
|
||||
|
|
@ -512,7 +512,7 @@ where
|
|||
let mut iter = db.scan_prefix(keys::EVENTS_PREFIX.as_bytes()).await?;
|
||||
let mut events = Vec::new();
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
let Some(seq) = keys::parse_event_seq(&key) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -535,7 +535,7 @@ where
|
|||
let mut iter = db.scan_prefix(keys::CHECKPOINTS_PREFIX.as_bytes()).await?;
|
||||
let mut checkpoints = Vec::new();
|
||||
while let Some(entry) = iter.next().await? {
|
||||
let key = key_to_string(entry.key)?;
|
||||
let key = key_to_string(&entry.key)?;
|
||||
let Some(seq) = keys::parse_checkpoint_seq(&key) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -545,7 +545,7 @@ where
|
|||
Ok(checkpoints)
|
||||
}
|
||||
|
||||
fn key_to_string(key: Bytes) -> Result<String> {
|
||||
fn key_to_string(key: &Bytes) -> Result<String> {
|
||||
String::from_utf8(key.to_vec())
|
||||
.map_err(|err| StoreError::Other(format!("stored key is not valid UTF-8: {err}")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ pub fn compute_cli_id() -> Result<String> {
|
|||
.context("no MAC address found")?;
|
||||
|
||||
let digest = md5::compute(mac.bytes());
|
||||
Ok(format!("{:x}", digest))
|
||||
Ok(format!("{digest:x}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use crate::event::Track;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct BufferPolicy {
|
||||
pub count_threshold: usize,
|
||||
pub time_threshold: Duration,
|
||||
|
|
@ -18,7 +19,7 @@ impl Default for BufferPolicy {
|
|||
}
|
||||
|
||||
pub(crate) fn consumer_loop(
|
||||
rx: Receiver<Track>,
|
||||
rx: &Receiver<Track>,
|
||||
config: BufferPolicy,
|
||||
mid_flush: impl Fn(&[Track]),
|
||||
final_flush: impl Fn(&[Track]),
|
||||
|
|
@ -92,7 +93,7 @@ mod tests {
|
|||
drop(tx);
|
||||
|
||||
consumer_loop(
|
||||
rx,
|
||||
&rx,
|
||||
BufferPolicy {
|
||||
count_threshold: 2,
|
||||
time_threshold: Duration::from_secs(60),
|
||||
|
|
@ -127,7 +128,7 @@ mod tests {
|
|||
drop(tx);
|
||||
|
||||
consumer_loop(
|
||||
rx,
|
||||
&rx,
|
||||
BufferPolicy {
|
||||
count_threshold: 2,
|
||||
time_threshold: Duration::from_secs(60),
|
||||
|
|
@ -157,7 +158,7 @@ mod tests {
|
|||
// Don't drop yet — let time threshold fire
|
||||
let handle = std::thread::spawn(move || {
|
||||
consumer_loop(
|
||||
rx,
|
||||
&rx,
|
||||
BufferPolicy {
|
||||
count_threshold: 100, // won't trigger
|
||||
time_threshold: Duration::from_millis(50),
|
||||
|
|
@ -196,7 +197,7 @@ mod tests {
|
|||
drop(tx); // disconnect immediately, below count threshold
|
||||
|
||||
consumer_loop(
|
||||
rx,
|
||||
&rx,
|
||||
BufferPolicy {
|
||||
count_threshold: 100, // won't trigger
|
||||
time_threshold: Duration::from_secs(60),
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ fn init_inner(level: TelemetryLevel, anonymous_id: String) {
|
|||
.name("telemetry".to_string())
|
||||
.spawn(move || {
|
||||
buffer::consumer_loop(
|
||||
rx,
|
||||
&rx,
|
||||
buffer::BufferPolicy::default(),
|
||||
|tracks| {
|
||||
if let Err(err) = sender::upload_blocking(tracks) {
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ fn report_panic(info: &PanicHookInfo<'_>) {
|
|||
}
|
||||
|
||||
let event = build_event(&message);
|
||||
spawn_panic_sender(event);
|
||||
spawn_panic_sender(&event);
|
||||
}
|
||||
|
||||
/// Serialize the Sentry event to a temp file and spawn `fabro __send_panic <path>`.
|
||||
fn spawn_panic_sender(event: Event<'static>) {
|
||||
fn spawn_panic_sender(event: &Event<'static>) {
|
||||
let Ok(json) = serde_json::to_vec(&event) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -174,8 +174,7 @@ mod tests {
|
|||
#[test]
|
||||
fn send_panic_noops_without_dsn() {
|
||||
// SENTRY_DSN is not set at compile time in tests, so this should error.
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let result = rt.block_on(capture(Path::new("/nonexistent")));
|
||||
let result = capture(Path::new("/nonexistent"));
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("SENTRY_DSN not set"));
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ fn normalize_issue(node: &Value) -> Result<Issue, String> {
|
|||
.map(std::string::ToString::to_string);
|
||||
let priority = match node["priority"].as_i64() {
|
||||
Some(0) | None => None,
|
||||
Some(n) => Some(n as i32),
|
||||
Some(n) => Some(i32::try_from(n).unwrap()),
|
||||
};
|
||||
let state = node["state"]["name"]
|
||||
.as_str()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::hash::Hash;
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub trait Combine {
|
||||
#[must_use]
|
||||
fn combine(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ impl AttrValue {
|
|||
pub fn is_llm_handler_type(handler_type: Option<&str>) -> bool {
|
||||
matches!(
|
||||
handler_type,
|
||||
Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot")
|
||||
Some("agent" | "agent_loop" | "prompt" | "one_shot")
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -428,6 +428,7 @@ impl Graph {
|
|||
/// Graph-level `loop_restart_signature_limit` (default 3).
|
||||
/// When the same failure signature repeats this many times, the pipeline aborts.
|
||||
pub fn loop_restart_signature_limit(&self) -> usize {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // filtered >= 1 above
|
||||
self.attrs
|
||||
.get("loop_restart_signature_limit")
|
||||
.and_then(AttrValue::as_i64)
|
||||
|
|
|
|||
|
|
@ -180,8 +180,7 @@ impl HookDefinition {
|
|||
format!("{event_str}:{short}")
|
||||
}
|
||||
Some(HookType::Http { ref url, .. }) => format!("{event_str}:{url}"),
|
||||
Some(HookType::Prompt { ref prompt, .. })
|
||||
| Some(HookType::Agent { ref prompt, .. }) => {
|
||||
Some(HookType::Prompt { ref prompt, .. } | HookType::Agent { ref prompt, .. }) => {
|
||||
let short = &prompt[..prompt.floor_char_boundary(20)];
|
||||
format!("{event_str}:{short}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,16 +39,16 @@ impl RunStatus {
|
|||
matches!(
|
||||
(self, to),
|
||||
(Self::Submitted, Self::Starting)
|
||||
| (Self::Starting, Self::Running)
|
||||
| (Self::Starting, Self::Failed)
|
||||
| (Self::Running, Self::Succeeded)
|
||||
| (Self::Running, Self::Failed)
|
||||
| (Self::Running, Self::Paused)
|
||||
| (Self::Running, Self::Removing)
|
||||
| (Self::Paused, Self::Running)
|
||||
| (Self::Paused, Self::Failed)
|
||||
| (Self::Starting | Self::Paused, Self::Running)
|
||||
| (
|
||||
Self::Starting | Self::Running | Self::Paused | Self::Removing,
|
||||
Self::Failed
|
||||
)
|
||||
| (
|
||||
Self::Running,
|
||||
Self::Succeeded | Self::Paused | Self::Removing
|
||||
)
|
||||
| (Self::Paused, Self::Removing)
|
||||
| (Self::Removing, Self::Failed)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ fn main() {
|
|||
// Entropy
|
||||
match rule.entropy {
|
||||
Some(e) => {
|
||||
let _ = writeln!(code, " entropy: Some({:.1}),", e);
|
||||
let _ = writeln!(code, " entropy: Some({e:.1}),");
|
||||
}
|
||||
None => code.push_str(" entropy: None,\n"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,11 +66,12 @@ impl CheckReport {
|
|||
footer: Option<&str>,
|
||||
max_width: Option<u16>,
|
||||
) -> String {
|
||||
let mut out = String::new();
|
||||
let width = max_width.unwrap_or(80) as usize;
|
||||
// " • " is 8 chars of prefix before detail text
|
||||
const DETAIL_PREFIX_LEN: usize = 8;
|
||||
|
||||
let mut out = String::new();
|
||||
let width = max_width.unwrap_or(80) as usize;
|
||||
|
||||
let show_section_headers = self.sections.len() > 1;
|
||||
|
||||
writeln!(out, "{}", s.bold.apply_to(&self.title)).unwrap();
|
||||
|
|
|
|||
|
|
@ -30,50 +30,49 @@ fn should_skip_object(obj: &serde_json::Map<String, Value>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn walk_replacements(
|
||||
v: &Value,
|
||||
seen: &mut std::collections::HashSet<String>,
|
||||
repls: &mut Vec<(String, String)>,
|
||||
) {
|
||||
match v {
|
||||
Value::Object(obj) => {
|
||||
if should_skip_object(obj) {
|
||||
return;
|
||||
}
|
||||
for (k, child) in obj {
|
||||
if should_skip_field(k) {
|
||||
continue;
|
||||
}
|
||||
walk_replacements(child, seen, repls);
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
for child in arr {
|
||||
walk_replacements(child, seen, repls);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
let redacted = super::redact_string(s);
|
||||
if redacted != *s && seen.insert(s.clone()) {
|
||||
repls.push((s.clone(), redacted));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk a parsed JSON value and collect (original, redacted) string pairs.
|
||||
fn collect_replacements(v: &Value) -> Vec<(String, String)> {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut repls = Vec::new();
|
||||
|
||||
fn walk(
|
||||
v: &Value,
|
||||
seen: &mut std::collections::HashSet<String>,
|
||||
repls: &mut Vec<(String, String)>,
|
||||
) {
|
||||
match v {
|
||||
Value::Object(obj) => {
|
||||
if should_skip_object(obj) {
|
||||
return;
|
||||
}
|
||||
for (k, child) in obj {
|
||||
if should_skip_field(k) {
|
||||
continue;
|
||||
}
|
||||
walk(child, seen, repls);
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
for child in arr {
|
||||
walk(child, seen, repls);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
let redacted = super::redact_string(s);
|
||||
if redacted != *s && seen.insert(s.clone()) {
|
||||
repls.push((s.clone(), redacted));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
walk(v, &mut seen, &mut repls);
|
||||
walk_replacements(v, &mut seen, &mut repls);
|
||||
repls
|
||||
}
|
||||
|
||||
/// JSON-encode a string value (with quotes), without HTML escaping.
|
||||
fn json_encode_string(s: &str) -> String {
|
||||
serde_json::to_string(s).unwrap_or_else(|_| format!("\"{}\"", s))
|
||||
serde_json::to_string(s).unwrap_or_else(|_| format!("\"{s}\""))
|
||||
}
|
||||
|
||||
/// Redact secrets in a single JSONL line.
|
||||
|
|
|
|||
|
|
@ -1100,10 +1100,11 @@ fn rename_fields(event_name: &str, fields: &mut serde_json::Map<String, serde_js
|
|||
|
||||
/// Current time as epoch milliseconds.
|
||||
fn epoch_millis() -> i64 {
|
||||
std::time::SystemTime::now()
|
||||
let millis = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64
|
||||
.as_millis();
|
||||
i64::try_from(millis).unwrap()
|
||||
}
|
||||
|
||||
/// Listener callback type for workflow run events.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ impl NodeSpec for WorkflowNode {
|
|||
}
|
||||
|
||||
fn max_visits(&self) -> Option<usize> {
|
||||
self.0.max_visits().map(|v| v.max(0) as usize)
|
||||
self.0
|
||||
.max_visits()
|
||||
.map(|v| usize::try_from(v.max(0)).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ use tokio::fs;
|
|||
use super::{EngineServices, Handler};
|
||||
|
||||
fn timeout_ms(node: &Node) -> Option<u64> {
|
||||
node.timeout().map(|d| d.as_millis() as u64)
|
||||
node.timeout()
|
||||
.map(|d| u64::try_from(d.as_millis()).unwrap())
|
||||
}
|
||||
|
||||
/// Shell-escape a string using `shlex::try_quote` (POSIX-safe).
|
||||
|
|
@ -107,7 +108,9 @@ impl Handler for CommandHandler {
|
|||
script.to_string()
|
||||
};
|
||||
|
||||
let timeout_ms = node.timeout().map_or(600_000, |d| d.as_millis() as u64);
|
||||
let timeout_ms = node
|
||||
.timeout()
|
||||
.map_or(600_000, |d| u64::try_from(d.as_millis()).unwrap());
|
||||
let env_vars = if services.env.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -440,6 +440,7 @@ fn build_compact_preamble(
|
|||
// Summary preamble
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SummaryDetail {
|
||||
Low,
|
||||
Medium,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ pub trait Handler: Send + Sync {
|
|||
}
|
||||
|
||||
/// Extract a human-readable message from a panic payload.
|
||||
pub(crate) fn format_panic_message(payload: Box<dyn Any + Send>) -> String {
|
||||
pub(crate) fn format_panic_message(payload: &Box<dyn Any + Send>) -> String {
|
||||
if let Some(s) = payload.downcast_ref::<&str>() {
|
||||
format!("handler panicked: {s}")
|
||||
} else if let Some(s) = payload.downcast_ref::<String>() {
|
||||
|
|
|
|||
|
|
@ -128,6 +128,15 @@ impl Handler for ParallelHandler {
|
|||
run_dir: &Path,
|
||||
services: &EngineServices,
|
||||
) -> Result<Outcome, FabroError> {
|
||||
// Build per-branch sandboxes (sequentially for git setup)
|
||||
struct BranchSetup {
|
||||
target_id: String,
|
||||
branch_index: usize,
|
||||
branch_context: Context,
|
||||
sandbox: Arc<dyn Sandbox>,
|
||||
worktree_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
let parallel_start = Instant::now();
|
||||
let branches = graph.outgoing_edges(&node.id);
|
||||
if branches.is_empty() {
|
||||
|
|
@ -188,15 +197,6 @@ impl Handler for ParallelHandler {
|
|||
None
|
||||
};
|
||||
|
||||
// Build per-branch sandboxes (sequentially for git setup)
|
||||
struct BranchSetup {
|
||||
target_id: String,
|
||||
branch_index: usize,
|
||||
branch_context: Context,
|
||||
sandbox: Arc<dyn Sandbox>,
|
||||
worktree_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
let mut branch_setups: Vec<BranchSetup> = Vec::new();
|
||||
for (branch_index, edge) in branches.iter().enumerate() {
|
||||
let target_id = edge.to.clone();
|
||||
|
|
|
|||
|
|
@ -164,7 +164,10 @@ impl RunLifecycle<WorkflowGraph> for EventLifecycle {
|
|||
index: stage_index,
|
||||
attempt: ctx.attempt as usize,
|
||||
max_attempts: ctx.result.max_attempts as usize,
|
||||
delay_ms: ctx.backoff_delay.map(|d| d.as_millis() as u64).unwrap_or(0),
|
||||
delay_ms: ctx
|
||||
.backoff_delay
|
||||
.map(|d| u64::try_from(d.as_millis()).unwrap())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -183,7 +186,7 @@ impl RunLifecycle<WorkflowGraph> for EventLifecycle {
|
|||
}
|
||||
let gv = node.inner();
|
||||
let stage_index = state.stage_index;
|
||||
let duration_ms = result.duration.as_millis() as u64;
|
||||
let duration_ms = u64::try_from(result.duration.as_millis()).unwrap();
|
||||
|
||||
if outcome.status == StageStatus::Fail {
|
||||
self.emitter.emit(&WorkflowRunEvent::StageFailed {
|
||||
|
|
@ -286,7 +289,8 @@ impl RunLifecycle<WorkflowGraph> for EventLifecycle {
|
|||
if state.cancelled {
|
||||
return;
|
||||
}
|
||||
let duration_ms = self.run_start.lock().unwrap().elapsed().as_millis() as u64;
|
||||
let duration_ms =
|
||||
u64::try_from(self.run_start.lock().unwrap().elapsed().as_millis()).unwrap();
|
||||
let artifact_count = self.artifact_store.lock().unwrap().list().len();
|
||||
let last_sha = self.last_git_sha.lock().unwrap().clone();
|
||||
let total_cost = {
|
||||
|
|
|
|||
|
|
@ -77,15 +77,15 @@ pub(crate) struct WorkflowLifecycle {
|
|||
impl WorkflowLifecycle {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
emitter: Arc<EventEmitter>,
|
||||
emitter: &Arc<EventEmitter>,
|
||||
hook_runner: Option<Arc<HookRunner>>,
|
||||
sandbox: Arc<dyn Sandbox>,
|
||||
sandbox: &Arc<dyn Sandbox>,
|
||||
graph: Arc<GvGraph>,
|
||||
run_dir: PathBuf,
|
||||
run_options: Arc<RunOptions>,
|
||||
run_dir: &PathBuf,
|
||||
run_options: &Arc<RunOptions>,
|
||||
is_resume: bool,
|
||||
) -> Self {
|
||||
let runtime_state = RuntimeState::new(&run_dir);
|
||||
let runtime_state = RuntimeState::new(run_dir);
|
||||
let restarted_from: Arc<Mutex<Option<(String, String)>>> = Arc::new(Mutex::new(None));
|
||||
let loop_restart_signature_limit = graph.loop_restart_signature_limit();
|
||||
let checkpoint_git_result: Arc<Mutex<Option<GitCheckpointResult>>> =
|
||||
|
|
@ -110,7 +110,7 @@ impl WorkflowLifecycle {
|
|||
};
|
||||
|
||||
let event = EventLifecycle {
|
||||
emitter: Arc::clone(&emitter),
|
||||
emitter: Arc::clone(emitter),
|
||||
graph_name: graph.name.clone(),
|
||||
run_id: run_options.run_id.clone(),
|
||||
run_start: Mutex::new(Instant::now()),
|
||||
|
|
@ -127,7 +127,7 @@ impl WorkflowLifecycle {
|
|||
|
||||
let hook = HookLifecycle {
|
||||
hook_runner,
|
||||
sandbox: Arc::clone(&sandbox),
|
||||
sandbox: Arc::clone(sandbox),
|
||||
hook_work_dir: working_directory.clone().map(PathBuf::from),
|
||||
run_id: run_options.run_id.clone(),
|
||||
graph_name: graph.name.clone(),
|
||||
|
|
@ -139,8 +139,8 @@ impl WorkflowLifecycle {
|
|||
run_dir: run_dir.clone(),
|
||||
run_id: run_options.run_id.clone(),
|
||||
graph: Arc::clone(&graph),
|
||||
run_options: Arc::clone(&run_options),
|
||||
emitter: Arc::clone(&emitter),
|
||||
run_options: Arc::clone(run_options),
|
||||
emitter: Arc::clone(emitter),
|
||||
circuit_breaker: Arc::clone(&circuit_breaker),
|
||||
checkpoint_enabled: true,
|
||||
};
|
||||
|
|
@ -148,22 +148,22 @@ impl WorkflowLifecycle {
|
|||
let start_node_id = graph.find_start_node().map(|n| n.id.clone());
|
||||
|
||||
let git = GitLifecycle {
|
||||
sandbox: Arc::clone(&sandbox),
|
||||
sandbox: Arc::clone(sandbox),
|
||||
artifact_store: Arc::clone(&artifact_store),
|
||||
emitter: Arc::clone(&emitter),
|
||||
emitter: Arc::clone(emitter),
|
||||
run_dir: run_dir.clone(),
|
||||
run_id: run_options.run_id.clone(),
|
||||
run_options: Arc::clone(&run_options),
|
||||
run_options: Arc::clone(run_options),
|
||||
start_node_id,
|
||||
checkpoint_git_result: Arc::clone(&checkpoint_git_result),
|
||||
last_git_sha: Arc::clone(&last_git_sha),
|
||||
};
|
||||
|
||||
let artifact = ArtifactLifecycle::new(
|
||||
Arc::clone(&sandbox),
|
||||
Arc::clone(sandbox),
|
||||
Arc::clone(&artifact_store),
|
||||
Some(runtime_state.artifact_values_dir()),
|
||||
Arc::clone(&emitter),
|
||||
Arc::clone(emitter),
|
||||
runtime_state.assets_dir(),
|
||||
run_options.asset_globs().to_vec(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl NodeHandler<WorkflowGraph> for WorkflowNodeHandler {
|
|||
}))
|
||||
}
|
||||
Err(panic_payload) => {
|
||||
let msg = format_panic_message(panic_payload);
|
||||
let msg = format_panic_message(&panic_payload);
|
||||
let visit = context.node_visit_count().max(1);
|
||||
let panic_dir = run_dir::node_dir(&self.run_dir, &gv_node.id, visit);
|
||||
let _ = std::fs::create_dir_all(&panic_dir);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub struct ForkRunInput {
|
|||
/// Create a new run that branches from an existing run at a specific checkpoint.
|
||||
///
|
||||
/// Returns the new run ID.
|
||||
pub fn fork(store: &Store, input: ForkRunInput) -> Result<String> {
|
||||
pub fn fork(store: &Store, input: &ForkRunInput) -> Result<String> {
|
||||
let timeline = build_timeline(store, &input.source_run_id)?;
|
||||
let entry = match input.target.as_ref() {
|
||||
Some(target) => timeline.resolve(target)?,
|
||||
|
|
@ -39,7 +39,7 @@ fn fork_from_entry(
|
|||
let new_run_id = ulid::Ulid::new().to_string();
|
||||
let sig = Signature::now("Fabro", "noreply@fabro.sh")?;
|
||||
|
||||
let new_run_branch = format!("{}{new_run_id}", RUN_BRANCH_PREFIX);
|
||||
let new_run_branch = format!("{RUN_BRANCH_PREFIX}{new_run_id}");
|
||||
match &entry.run_commit_sha {
|
||||
Some(sha) => {
|
||||
let oid =
|
||||
|
|
@ -134,7 +134,7 @@ fn fork_from_entry(
|
|||
.map_err(|e| anyhow::anyhow!("failed to write metadata entries: {e}"))?;
|
||||
|
||||
if push {
|
||||
let source_run_branch = format!("{}{source_run_id}", RUN_BRANCH_PREFIX);
|
||||
let source_run_branch = format!("{RUN_BRANCH_PREFIX}{source_run_id}");
|
||||
let run_refspec = format!("refs/heads/{new_run_branch}:refs/heads/{new_run_branch}");
|
||||
let meta_refspec = format!("refs/heads/{new_meta_branch}:refs/heads/{new_meta_branch}");
|
||||
push_run_branches(
|
||||
|
|
@ -246,7 +246,7 @@ mod tests {
|
|||
|
||||
let new_run_id = fork(
|
||||
&store,
|
||||
ForkRunInput {
|
||||
&ForkRunInput {
|
||||
source_run_id: source_run_id.to_string(),
|
||||
target: Some(RewindTarget::from_str("@2").unwrap()),
|
||||
push: false,
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ fn backfill_run_shas(store: &Store, run_id: &str, timeline: &mut [TimelineEntry]
|
|||
return;
|
||||
}
|
||||
|
||||
let run_branch = format!("{}{run_id}", RUN_BRANCH_PREFIX);
|
||||
let run_branch = format!("{RUN_BRANCH_PREFIX}{run_id}");
|
||||
let Ok(sig) = Signature::now("Fabro", "noreply@fabro.sh") else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -241,7 +241,7 @@ fn detect_parallel_interior(graph: &Graph) -> HashMap<String, String> {
|
|||
interior_map
|
||||
}
|
||||
|
||||
pub fn rewind(store: &Store, input: RewindInput) -> Result<()> {
|
||||
pub fn rewind(store: &Store, input: &RewindInput) -> Result<()> {
|
||||
let timeline = build_timeline(store, &input.run_id)?;
|
||||
let entry = timeline.resolve(&input.target)?;
|
||||
rewind_to_entry(store, &input.run_id, entry, input.push)
|
||||
|
|
@ -258,7 +258,7 @@ fn rewind_to_entry(store: &Store, run_id: &str, entry: &TimelineEntry, push: boo
|
|||
entry.ordinal, entry.node_name
|
||||
);
|
||||
|
||||
let run_branch = format!("{}{run_id}", RUN_BRANCH_PREFIX);
|
||||
let run_branch = format!("{RUN_BRANCH_PREFIX}{run_id}");
|
||||
match &entry.run_commit_sha {
|
||||
Some(sha) => {
|
||||
let oid =
|
||||
|
|
@ -494,7 +494,7 @@ mod tests {
|
|||
|
||||
rewind(
|
||||
&store,
|
||||
RewindInput {
|
||||
&RewindInput {
|
||||
run_id: "run-1".to_string(),
|
||||
target: RewindTarget::Ordinal(1),
|
||||
push: false,
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ fn persist_terminal_engine_failure(run_dir: &Path, error: &FabroError, duration:
|
|||
run_dir,
|
||||
final_status,
|
||||
failure_reason,
|
||||
duration.as_millis() as u64,
|
||||
u64::try_from(duration.as_millis()).unwrap(),
|
||||
None,
|
||||
);
|
||||
persist_terminal_outcome(run_dir, &conclusion, run_status, status_reason);
|
||||
|
|
@ -472,7 +472,7 @@ impl RunSession {
|
|||
let executed = pipeline::execute(initialized).await;
|
||||
let failed = !matches!(
|
||||
executed.outcome.as_ref().map(|outcome| &outcome.status),
|
||||
Ok(StageStatus::Success) | Ok(StageStatus::PartialSuccess)
|
||||
Ok(StageStatus::Success | StageStatus::PartialSuccess)
|
||||
);
|
||||
|
||||
let retro_opts = RetroOptions {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub trait OutcomeExt: Sized {
|
|||
fn fail_classify(reason: impl Into<String>) -> Self;
|
||||
fn retry_classify(reason: impl Into<String>) -> Self;
|
||||
fn simulated(node_id: &str) -> Self;
|
||||
#[must_use]
|
||||
fn with_signature(self, sig: Option<impl Into<String>>) -> Self;
|
||||
fn failure_reason(&self) -> Option<&str>;
|
||||
fn failure_category(&self) -> Option<FailureCategory>;
|
||||
|
|
|
|||
|
|
@ -85,12 +85,12 @@ pub async fn execute(init: Initialized) -> Executed {
|
|||
|
||||
let settings_arc = Arc::new(run_options.clone());
|
||||
let lifecycle = WorkflowLifecycle::new(
|
||||
Arc::clone(&emitter),
|
||||
&emitter,
|
||||
hook_runner.clone(),
|
||||
Arc::clone(&sandbox),
|
||||
&sandbox,
|
||||
graph_arc,
|
||||
run_options.run_dir.clone(),
|
||||
settings_arc,
|
||||
&run_options.run_dir,
|
||||
&settings_arc,
|
||||
checkpoint.is_some(),
|
||||
);
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ pub async fn execute(init: Initialized) -> Executed {
|
|||
|
||||
let graph_max = graph.max_node_visits();
|
||||
let max_node_visits = if graph_max > 0 {
|
||||
Some(graph_max as usize)
|
||||
Some(usize::try_from(graph_max).unwrap())
|
||||
} else if run_options.dry_run_enabled() {
|
||||
Some(10)
|
||||
} else {
|
||||
|
|
@ -228,12 +228,15 @@ pub async fn execute(init: Initialized) -> Executed {
|
|||
return;
|
||||
}
|
||||
let last = emitter.last_event_at();
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64;
|
||||
let now = i64::try_from(
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis(),
|
||||
)
|
||||
.unwrap();
|
||||
let idle_ms = now.saturating_sub(last);
|
||||
if idle_ms >= stall_timeout.as_millis() as i64 {
|
||||
if idle_ms >= i64::try_from(stall_timeout.as_millis()).unwrap() {
|
||||
token_clone.cancel();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ fn format_duration_ms(ms: u64) -> String {
|
|||
if secs >= 60 {
|
||||
format!("{}m {}s", secs / 60, secs % 60)
|
||||
} else {
|
||||
format!("{}s", secs)
|
||||
format!("{secs}s")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ pub async fn run_retro(options: &RetroOptions, dry_run: bool) -> Option<Retro> {
|
|||
Err(anyhow::anyhow!("No LLM client available"))
|
||||
};
|
||||
|
||||
let duration_ms = retro_start.elapsed().as_millis() as u64;
|
||||
let duration_ms = u64::try_from(retro_start.elapsed().as_millis()).unwrap();
|
||||
if let Some(ref emitter) = options.emitter {
|
||||
match &narrative_result {
|
||||
Ok(_) => emitter.emit(&WorkflowRunEvent::RetroCompleted { duration_ms }),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue