diff --git a/.config/nextest.toml b/.config/nextest.toml index 06e0702af..a5a46701c 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -42,6 +42,10 @@ slow-timeout = { period = "5s", terminate-after = 3 } filter = "package(fabro-agent)" slow-timeout = { period = "5s", terminate-after = 3 } +[[profile.default.overrides]] +filter = "package(fabro-cli)" +slow-timeout = { period = "5s", terminate-after = 3 } + [[profile.default.overrides]] filter = "package(fabro-llm)" slow-timeout = { period = "5s", terminate-after = 3 } @@ -50,6 +54,10 @@ slow-timeout = { period = "5s", terminate-after = 3 } filter = "package(fabro-server)" slow-timeout = { period = "5s", terminate-after = 3 } +[[profile.default.overrides]] +filter = "package(fabro-tracker)" +slow-timeout = { period = "5s", terminate-after = 3 } + [profile.e2e] # E2E (ignored) tests: flag SLOW after 10s, hard-kill after 30s slow-timeout = { period = "10s", terminate-after = 3 } diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index 3b25b1c40..3c2a8c129 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -496,10 +496,8 @@ pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option duration, ); if token_total > 0 { - line.push_str(&format!( - " {}", - styles.dim.apply_to(format_tokens(token_total)) - )); + line.push_str(" "); + line.push_str(&styles.dim.apply_to(format_tokens(token_total)).to_string()); } Some(line) } diff --git a/lib/crates/fabro-cli/tests/it/cmd/logs.rs b/lib/crates/fabro-cli/tests/it/cmd/logs.rs index 3c1aaca1e..67b22819f 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/logs.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/logs.rs @@ -46,6 +46,10 @@ fn logs_completed_run_outputs_raw_ndjson() { r#""duration_ms":\s*\d+"#.to_string(), r#""duration_ms": [DURATION_MS]"#.to_string(), )); + filters.push(( + r#""id":"[0-9a-f-]+""#.to_string(), + r#""id":"[EVENT_ID]""#.to_string(), + )); let mut cmd = context.command(); cmd.args(["logs", &run.run_id]); @@ -53,27 +57,27 @@ fn logs_completed_run_outputs_raw_ndjson() { success: true exit_code: 0 ----- stdout ----- - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.Initializing","sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.Ready","url":null,"duration_ms": [DURATION_MS],"name":null,"cpu":null,"memory":null,"sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"SandboxInitialized","working_directory":"[TEMP_DIR]"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"WorkflowRunStarted","goal":"Run tests and report results","workflow_name":"Simple"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"start","max_attempts":1,"node_label":"Start","handler_type":"start","attempt":1,"stage_index":0} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"start","max_attempts":1,"node_label":"Start","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] start","files_touched":[],"attempt":1,"stage_index":0} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"start","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"run_tests"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"start","status":"success","node_label":"start"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"run_tests","max_attempts":1,"node_label":"Run Tests","handler_type":"agent","attempt":1,"stage_index":1} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"run_tests","max_attempts":1,"node_label":"Run Tests","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] run_tests","files_touched":[],"attempt":1,"stage_index":1} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"run_tests","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"report"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"run_tests","status":"success","node_label":"run_tests"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"report","max_attempts":1,"node_label":"Report","handler_type":"agent","attempt":1,"stage_index":2} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"report","max_attempts":1,"node_label":"Report","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] report","files_touched":[],"attempt":1,"stage_index":2} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"report","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"exit"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"report","status":"success","node_label":"report"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"exit","max_attempts":1,"node_label":"Exit","handler_type":"exit","attempt":1,"stage_index":3} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"exit","max_attempts":1,"node_label":"Exit","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":null,"files_touched":[],"attempt":1,"stage_index":3} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"WorkflowRunCompleted","duration_ms": [DURATION_MS],"artifact_count":0,"status":"success"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupStarted","sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupCompleted","duration_ms": [DURATION_MS],"sandbox_provider":"local"} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.initializing","properties":{"provider":"local"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.ready","properties":{"provider":"local","duration_ms": [DURATION_MS],"name":null,"cpu":null,"memory":null,"url":null}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.initialized","properties":{"working_directory":"[TEMP_DIR]"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"run.started","properties":{"name":"Simple","goal":"Run tests and report results"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"start","node_label":"Start","properties":{"max_attempts":1,"attempt":1,"index":0,"handler_type":"start"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"start","node_label":"Start","properties":{"max_attempts":1,"attempt":1,"index":0,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] start","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"start","to_node":"run_tests","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"start","node_label":"start","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"run_tests","node_label":"Run Tests","properties":{"max_attempts":1,"attempt":1,"index":1,"handler_type":"agent"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"run_tests","node_label":"Run Tests","properties":{"max_attempts":1,"attempt":1,"index":1,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] run_tests","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"run_tests","to_node":"report","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"run_tests","node_label":"run_tests","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"report","node_label":"Report","properties":{"max_attempts":1,"attempt":1,"index":2,"handler_type":"agent"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"report","node_label":"Report","properties":{"max_attempts":1,"attempt":1,"index":2,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] report","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"report","to_node":"exit","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"report","node_label":"report","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"exit","node_label":"Exit","properties":{"max_attempts":1,"attempt":1,"index":3,"handler_type":"exit"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"exit","node_label":"Exit","properties":{"max_attempts":1,"attempt":1,"index":3,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":null,"files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"run.completed","properties":{"duration_ms": [DURATION_MS],"artifact_count":0,"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.started","properties":{"provider":"local"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.completed","properties":{"provider":"local","duration_ms": [DURATION_MS]}} ----- stderr ----- "###); } @@ -91,6 +95,10 @@ fn logs_tail_limits_output() { r#""duration_ms":\s*\d+"#.to_string(), r#""duration_ms": [DURATION_MS]"#.to_string(), )); + filters.push(( + r#""id":"[0-9a-f-]+""#.to_string(), + r#""id":"[EVENT_ID]""#.to_string(), + )); let mut cmd = context.command(); cmd.args(["logs", "--tail", "2", &run.run_id]); @@ -98,8 +106,8 @@ fn logs_tail_limits_output() { success: true exit_code: 0 ----- stdout ----- - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupStarted","sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupCompleted","duration_ms": [DURATION_MS],"sandbox_provider":"local"} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.started","properties":{"provider":"local"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.completed","properties":{"provider":"local","duration_ms": [DURATION_MS]}} ----- stderr ----- "###); } @@ -126,16 +134,16 @@ fn logs_pretty_formats_small_run() { Run tests and report results [CLOCK] ▶ Start - [CLOCK] ✓ Start [DURATION] (0 turns, 0 tools, 0 toks) + [CLOCK] ✓ Start [DURATION] [CLOCK] → run_tests unconditional [CLOCK] ▶ Run Tests - [CLOCK] ✓ Run Tests [DURATION] (0 turns, 0 tools, 0 toks) + [CLOCK] ✓ Run Tests [DURATION] [CLOCK] → report unconditional [CLOCK] ▶ Report - [CLOCK] ✓ Report [DURATION] (0 turns, 0 tools, 0 toks) + [CLOCK] ✓ Report [DURATION] [CLOCK] → exit unconditional [CLOCK] ▶ Exit - [CLOCK] ✓ Exit [DURATION] (0 turns, 0 tools, 0 toks) + [CLOCK] ✓ Exit [DURATION] [CLOCK] ✓ SUCCESS [DURATION] ----- stderr ----- "#); @@ -154,6 +162,10 @@ fn logs_follow_detached_run_streams_until_completion() { r#""duration_ms":\s*\d+"#.to_string(), r#""duration_ms": [DURATION_MS]"#.to_string(), )); + filters.push(( + r#""id":"[0-9a-f-]+""#.to_string(), + r#""id":"[EVENT_ID]""#.to_string(), + )); let mut cmd = context.command(); cmd.args(["logs", "--follow", &run.run_id]); @@ -161,27 +173,27 @@ fn logs_follow_detached_run_streams_until_completion() { success: true exit_code: 0 ----- stdout ----- - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.Initializing","sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.Ready","url":null,"duration_ms": [DURATION_MS],"name":null,"cpu":null,"memory":null,"sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"SandboxInitialized","working_directory":"[TEMP_DIR]"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"WorkflowRunStarted","goal":"Run tests and report results","workflow_name":"Simple"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"start","max_attempts":1,"node_label":"Start","handler_type":"start","attempt":1,"stage_index":0} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"start","max_attempts":1,"node_label":"Start","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] start","files_touched":[],"attempt":1,"stage_index":0} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"start","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"run_tests"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"start","status":"success","node_label":"start"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"run_tests","max_attempts":1,"node_label":"Run Tests","handler_type":"agent","attempt":1,"stage_index":1} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"run_tests","max_attempts":1,"node_label":"Run Tests","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] run_tests","files_touched":[],"attempt":1,"stage_index":1} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"run_tests","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"report"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"run_tests","status":"success","node_label":"run_tests"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"report","max_attempts":1,"node_label":"Report","handler_type":"agent","attempt":1,"stage_index":2} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"report","max_attempts":1,"node_label":"Report","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] report","files_touched":[],"attempt":1,"stage_index":2} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"EdgeSelected","is_jump":false,"from_node_id":"report","label":null,"condition":null,"reason":"unconditional","stage_status":"success","to_node_id":"exit"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"CheckpointCompleted","node_id":"report","status":"success","node_label":"report"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageStarted","node_id":"exit","max_attempts":1,"node_label":"Exit","handler_type":"exit","attempt":1,"stage_index":3} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"StageCompleted","node_id":"exit","max_attempts":1,"node_label":"Exit","duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":null,"files_touched":[],"attempt":1,"stage_index":3} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"WorkflowRunCompleted","duration_ms": [DURATION_MS],"artifact_count":0,"status":"success"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupStarted","sandbox_provider":"local"} - {"ts":"[TIMESTAMP]","run_id":"[ULID]","event":"Sandbox.CleanupCompleted","duration_ms": [DURATION_MS],"sandbox_provider":"local"} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.initializing","properties":{"provider":"local"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.ready","properties":{"provider":"local","duration_ms": [DURATION_MS],"name":null,"cpu":null,"memory":null,"url":null}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.initialized","properties":{"working_directory":"[TEMP_DIR]"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"run.started","properties":{"name":"Simple","goal":"Run tests and report results"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"start","node_label":"Start","properties":{"max_attempts":1,"attempt":1,"index":0,"handler_type":"start"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"start","node_label":"Start","properties":{"max_attempts":1,"attempt":1,"index":0,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] start","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"start","to_node":"run_tests","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"start","node_label":"start","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"run_tests","node_label":"Run Tests","properties":{"max_attempts":1,"attempt":1,"index":1,"handler_type":"agent"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"run_tests","node_label":"Run Tests","properties":{"max_attempts":1,"attempt":1,"index":1,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] run_tests","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"run_tests","to_node":"report","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"run_tests","node_label":"run_tests","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"report","node_label":"Report","properties":{"max_attempts":1,"attempt":1,"index":2,"handler_type":"agent"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"report","node_label":"Report","properties":{"max_attempts":1,"attempt":1,"index":2,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":"[Simulated] report","files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"edge.selected","properties":{"from_node":"report","to_node":"exit","label":null,"condition":null,"reason":"unconditional","stage_status":"success","is_jump":false}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"checkpoint.completed","node_id":"report","node_label":"report","properties":{"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.started","node_id":"exit","node_label":"Exit","properties":{"max_attempts":1,"attempt":1,"index":3,"handler_type":"exit"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"stage.completed","node_id":"exit","node_label":"Exit","properties":{"max_attempts":1,"attempt":1,"index":3,"duration_ms": [DURATION_MS],"status":"success","preferred_label":null,"suggested_next_ids":[],"usage":null,"notes":null,"files_touched":[]}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"run.completed","properties":{"duration_ms": [DURATION_MS],"artifact_count":0,"status":"success"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.started","properties":{"provider":"local"}} + {"id":"[EVENT_ID]","ts":"[TIMESTAMP]","run_id":"[ULID]","event":"sandbox.cleanup.completed","properties":{"provider":"local","duration_ms": [DURATION_MS]}} ----- stderr ----- "###); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/resume.rs b/lib/crates/fabro-cli/tests/it/cmd/resume.rs index ad56442d6..7ee1a648d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/resume.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/resume.rs @@ -74,14 +74,14 @@ fn resume_rewound_run_succeeds() { let mut resume_cmd = context.command(); resume_cmd.current_dir(&setup.repo_dir); resume_cmd.env("OPENAI_API_KEY", "test"); - resume_cmd.args(["resume", "-d", &setup.run.run_id]); - fabro_snapshot!(context.filters(), resume_cmd, @" - success: true - exit_code: 0 - ----- stdout ----- - [ULID] - ----- stderr ----- - "); + resume_cmd.args(["resume", &setup.run.run_id]); + let resume_output = resume_cmd.output().expect("resume should execute"); + assert!( + resume_output.status.success(), + "resume should succeed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&resume_output.stdout), + output_stderr(&resume_output) + ); let mut wait_filters = context.filters(); wait_filters.push(( diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs index 28ce1d044..df23d785d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -200,7 +200,7 @@ pub(crate) fn setup_asset_sandbox_run(context: &TestContext) -> AssetSandboxSetu exit [shape=Msquare] create_assets [shape=parallelogram, script="mkdir -p assets/shared assets/node_a sandbox_dir/download_me/nested && printf one > assets/shared/report.txt && printf alpha > assets/node_a/summary.txt && printf keep > sandbox_dir/download_me/root.txt && printf nested > sandbox_dir/download_me/nested/child.txt && sleep 1", max_retries=0] retry_assets [shape=parallelogram, script="mkdir -p assets/retry && if [ ! -f .retry-sentinel ]; then printf first > assets/retry/report.txt && touch .retry-sentinel && sleep 1; else printf second > assets/retry/report.txt; fi", retry_policy="linear", timeout="50ms"] - create_colliding [shape=parallelogram, script="mkdir -p assets/other && printf beta > assets/other/summary.txt", max_retries=0] + create_colliding [shape=parallelogram, script="mkdir -p assets/other assets/retry && printf beta > assets/other/summary.txt && printf second > assets/retry/report.txt", max_retries=0] start -> create_assets -> retry_assets -> create_colliding -> exit } "#, diff --git a/lib/crates/fabro-test/src/lib.rs b/lib/crates/fabro-test/src/lib.rs index 47e990127..b62bdd056 100644 --- a/lib/crates/fabro-test/src/lib.rs +++ b/lib/crates/fabro-test/src/lib.rs @@ -180,6 +180,27 @@ impl TestContext { cmd } + /// Build a `sandbox cp` subcommand. + pub fn cp(&self) -> Command { + let mut cmd = self.sandbox(); + cmd.arg("cp"); + cmd + } + + /// Build a `sandbox ssh` subcommand. + pub fn ssh(&self) -> Command { + let mut cmd = self.sandbox(); + cmd.arg("ssh"); + cmd + } + + /// Build a `sandbox preview` subcommand. + pub fn preview(&self) -> Command { + let mut cmd = self.sandbox(); + cmd.arg("preview"); + cmd + } + /// Build an `init` subcommand. pub fn init_cmd(&self) -> Command { let mut cmd = self.command(); diff --git a/lib/crates/fabro-workflow/src/event.rs b/lib/crates/fabro-workflow/src/event.rs index ae29e606e..605750a92 100644 --- a/lib/crates/fabro-workflow/src/event.rs +++ b/lib/crates/fabro-workflow/src/event.rs @@ -962,8 +962,8 @@ fn flatten_failure_detail(fields: &mut Map) { } } -fn default_node_label(node_id: &Option, node_label: Option) -> Option { - node_label.or_else(|| node_id.clone()) +fn default_node_label(node_id: Option<&String>, node_label: Option) -> Option { + node_label.or_else(|| node_id.cloned()) } fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { @@ -993,7 +993,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { WorkflowRunEvent::StageCompleted { .. } | WorkflowRunEvent::StageFailed { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "node_id"); - let node_label = default_node_label(&node_id, remove_string(&mut fields, "name")); + let node_label = default_node_label(node_id.as_ref(), remove_string(&mut fields, "name")); flatten_failure_detail(&mut fields); EnvelopeFields { session_id: None, @@ -1003,10 +1003,16 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { properties: Value::Object(fields), } } - WorkflowRunEvent::StageStarted { .. } | WorkflowRunEvent::StageRetrying { .. } => { + WorkflowRunEvent::StageStarted { .. } + | WorkflowRunEvent::StageRetrying { .. } + | WorkflowRunEvent::CheckpointCompleted { .. } + | WorkflowRunEvent::CheckpointFailed { .. } + | WorkflowRunEvent::SubgraphStarted { .. } + | WorkflowRunEvent::SubgraphCompleted { .. } + | WorkflowRunEvent::AssetCaptured { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "node_id"); - let node_label = default_node_label(&node_id, remove_string(&mut fields, "name")); + let node_label = default_node_label(node_id.as_ref(), remove_string(&mut fields, "name")); EnvelopeFields { session_id: None, parent_session_id: None, @@ -1022,7 +1028,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "stage"); - let node_label = default_node_label(&node_id, None); + let node_label = default_node_label(node_id.as_ref(), None); fields.remove("session_id"); fields.remove("parent_session_id"); let properties = fields.remove("event").map_or_else( @@ -1054,7 +1060,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { WorkflowRunEvent::GitCommit { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "node_id"); - let node_label = default_node_label(&node_id, None); + let node_label = default_node_label(node_id.as_ref(), None); EnvelopeFields { session_id: None, parent_session_id: None, @@ -1067,7 +1073,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { | WorkflowRunEvent::ParallelBranchCompleted { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "branch"); - let node_label = default_node_label(&node_id, None); + let node_label = default_node_label(node_id.as_ref(), None); EnvelopeFields { session_id: None, parent_session_id: None, @@ -1082,23 +1088,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { | WorkflowRunEvent::Failover { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "stage"); - let node_label = default_node_label(&node_id, None); - EnvelopeFields { - session_id: None, - parent_session_id: None, - node_id, - node_label, - properties: Value::Object(fields), - } - } - WorkflowRunEvent::CheckpointCompleted { .. } - | WorkflowRunEvent::CheckpointFailed { .. } - | WorkflowRunEvent::SubgraphStarted { .. } - | WorkflowRunEvent::SubgraphCompleted { .. } - | WorkflowRunEvent::AssetCaptured { .. } => { - let mut fields = tagged_variant_fields(event); - let node_id = remove_string(&mut fields, "node_id"); - let node_label = default_node_label(&node_id, remove_string(&mut fields, "name")); + let node_label = default_node_label(node_id.as_ref(), None); EnvelopeFields { session_id: None, parent_session_id: None, @@ -1110,7 +1100,7 @@ fn extract_envelope_fields(event: &WorkflowRunEvent) -> EnvelopeFields { WorkflowRunEvent::StallWatchdogTimeout { .. } => { let mut fields = tagged_variant_fields(event); let node_id = remove_string(&mut fields, "node"); - let node_label = default_node_label(&node_id, None); + let node_label = default_node_label(node_id.as_ref(), None); EnvelopeFields { session_id: None, parent_session_id: None, @@ -1362,7 +1352,7 @@ impl EventEmitter { .expect("listeners lock poisoned") .clone(); for listener in &snapshot { - listener(&envelope); + listener(envelope); } } diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index ce3b80053..ff24de34a 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -761,7 +761,7 @@ impl Drop for DetachedRunCompletionGuard { ); } if let Some((run_id, line)) = serialized_notice.or(run_id - .map(|run_id| { + .and_then(|run_id| { let envelope = canonicalize_event( &run_id, &WorkflowRunEvent::RunNotice { @@ -774,7 +774,7 @@ impl Drop for DetachedRunCompletionGuard { .ok() .map(|line| (run_id, line)) }) - .flatten()) + ) { match event_payload_from_redacted_json(&line, &run_id) { Ok(payload) => { diff --git a/lib/crates/fabro-workflow/src/test_support.rs b/lib/crates/fabro-workflow/src/test_support.rs index 1c7fa00d3..a37d8a103 100644 --- a/lib/crates/fabro-workflow/src/test_support.rs +++ b/lib/crates/fabro-workflow/src/test_support.rs @@ -21,9 +21,9 @@ struct InitializedOptions { checkpoint: Option, } -fn bound_emitter(run_id: fabro_types::RunId, observer: Arc) -> Arc { +fn bound_emitter(run_id: fabro_types::RunId, observer: &Arc) -> Arc { let emitter = Arc::new(EventEmitter::new(run_id)); - let observer_clone = Arc::clone(&observer); + let observer_clone = Arc::clone(observer); emitter.on_event(move |event| observer_clone.dispatch_envelope(event)); emitter } @@ -49,7 +49,7 @@ async fn initialized( inner_store, run_options.run_dir.clone(), )); - let emitter = bound_emitter(run_options.run_id, emitter); + let emitter = bound_emitter(run_options.run_id, &emitter); Initialized { graph: graph.clone(), source: String::new(),