From 427bfdb923d3cde71154f98b720b2e741e15003b Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Fri, 7 Aug 2026 23:20:56 -0700 Subject: [PATCH 1/3] style: apply cargo fmt across the workspace Purely mechanical: `cargo fmt --all` with default settings, no hand edits. Verified idempotent (a second run is a no-op), and 91 tests plus clippy --all-targets -D warnings are unchanged after it. Separated from the CI gate that follows so this diff can be skimmed as "rustfmt output" rather than reviewed line by line. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PfAfAujueuZ3rDTiL9apx3 --- crates/agent/src/lib.rs | 27 +++++++---- crates/auth/src/codex.rs | 4 +- crates/auth/src/lib.rs | 16 +++++-- crates/auth/src/openrouter.rs | 19 ++++++-- crates/auth/src/pkce.rs | 5 +- crates/cli/src/agents.rs | 33 ++++++++------ crates/cli/src/main.rs | 37 ++++++++------- crates/harness/src/lib.rs | 32 ++++++++++--- crates/harness/src/pen.rs | 62 +++++++++++++++++-------- crates/llm/src/anthropic.rs | 21 +++++++-- crates/llm/src/chatcompletions.rs | 15 ++++-- crates/llm/src/codex.rs | 40 ++++++++++++---- crates/llm/src/lib.rs | 4 +- crates/sandbox/src/lib.rs | 17 +++++-- crates/store/src/lib.rs | 76 +++++++++++++++++++++++-------- crates/store/src/recovery.rs | 22 +++++++-- crates/tools/src/bash.rs | 16 +++++-- crates/tools/src/fs.rs | 73 ++++++++++++++++++++++------- crates/tools/src/lib.rs | 9 +++- crates/tools/src/search.rs | 25 ++++++++-- 20 files changed, 406 insertions(+), 147 deletions(-) diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs index 72ea96c..baed126 100644 --- a/crates/agent/src/lib.rs +++ b/crates/agent/src/lib.rs @@ -232,10 +232,7 @@ impl Agent { tool_use_id: id.clone(), name: name.clone(), input: input.clone(), - replay_safe: self - .registry - .get(name) - .is_some_and(|t| t.replay_safe()), + replay_safe: self.registry.get(name).is_some_and(|t| t.replay_safe()), }), _ => None, }) @@ -346,7 +343,12 @@ impl Agent { (output, is_error) } - async fn run_tool(&self, name: &str, call_id: &str, input: serde_json::Value) -> (String, bool) { + async fn run_tool( + &self, + name: &str, + call_id: &str, + input: serde_json::Value, + ) -> (String, bool) { let Some(tool) = self.registry.get(name) else { return (format!("unknown tool: {name}"), true); }; @@ -526,7 +528,9 @@ mod tests { let mut agent = agent(provider); let out = agent.send("go").await.unwrap(); assert_eq!(out, "recovered"); - let ContentBlock::ToolResult { is_error, content, .. } = &agent.messages()[2].content[0] + let ContentBlock::ToolResult { + is_error, content, .. + } = &agent.messages()[2].content[0] else { panic!("expected tool result"); }; @@ -739,7 +743,11 @@ mod tests { self.0.lock().unwrap().push("results".into()); Ok(()) } - async fn run_finished(&mut self, outcome: RunOutcome, _: Usage) -> Result<(), JournalError> { + async fn run_finished( + &mut self, + outcome: RunOutcome, + _: Usage, + ) -> Result<(), JournalError> { self.0 .lock() .unwrap() @@ -824,6 +832,9 @@ mod tests { Event::TurnDone { .. } => "done", }); } - assert_eq!(kinds, vec!["text", "tool_start", "tool_end", "text", "done"]); + assert_eq!( + kinds, + vec!["text", "tool_start", "tool_end", "text", "done"] + ); } } diff --git a/crates/auth/src/codex.rs b/crates/auth/src/codex.rs index 92edac6..ad206a2 100644 --- a/crates/auth/src/codex.rs +++ b/crates/auth/src/codex.rs @@ -80,7 +80,9 @@ impl CodexAuth { ]) .await?; // A refresh response may omit a new refresh token; keep the old one. - if let Credential::Oauth { refresh_token: rt, .. } = &mut credential + if let Credential::Oauth { + refresh_token: rt, .. + } = &mut credential && rt.is_empty() { *rt = refresh_token.to_string(); diff --git a/crates/auth/src/lib.rs b/crates/auth/src/lib.rs index f87b87e..d5280e1 100644 --- a/crates/auth/src/lib.rs +++ b/crates/auth/src/lib.rs @@ -137,8 +137,13 @@ mod tests { let mut file = AuthFile::load(&path).unwrap(); assert!(file.get("openrouter").is_none()); - file.set("openrouter", Credential::ApiKey { key: "sk-or-x".into() }) - .unwrap(); + file.set( + "openrouter", + Credential::ApiKey { + key: "sk-or-x".into(), + }, + ) + .unwrap(); file.set( "codex", Credential::Oauth { @@ -153,7 +158,9 @@ mod tests { let reloaded = AuthFile::load(&path).unwrap(); assert_eq!( reloaded.get("openrouter"), - Some(&Credential::ApiKey { key: "sk-or-x".into() }) + Some(&Credential::ApiKey { + key: "sk-or-x".into() + }) ); assert!(matches!( reloaded.get("codex"), @@ -168,7 +175,8 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("auth.json"); let mut file = AuthFile::load(&path).unwrap(); - file.set("x", Credential::ApiKey { key: "k".into() }).unwrap(); + file.set("x", Credential::ApiKey { key: "k".into() }) + .unwrap(); let mode = std::fs::metadata(&path).unwrap().permissions().mode(); assert_eq!(mode & 0o777, 0o600); } diff --git a/crates/auth/src/openrouter.rs b/crates/auth/src/openrouter.rs index 7fbdbc0..6e0bb8b 100644 --- a/crates/auth/src/openrouter.rs +++ b/crates/auth/src/openrouter.rs @@ -70,7 +70,10 @@ pub async fn capture_code(listener: TcpListener) -> Result { let mut buf = vec![0u8; 8 * 1024]; let n = stream.read(&mut buf).await?; let request = String::from_utf8_lossy(&buf[..n]); - let Some(target) = request.lines().next().and_then(|l| l.split_whitespace().nth(1)) + let Some(target) = request + .lines() + .next() + .and_then(|l| l.split_whitespace().nth(1)) else { continue; }; @@ -91,9 +94,15 @@ pub async fn capture_code(listener: TcpListener) -> Result { continue; } let (status, page) = if code.is_some() { - ("200 OK", "bullpen is connected to OpenRouter. You can close this tab.") + ( + "200 OK", + "bullpen is connected to OpenRouter. You can close this tab.", + ) } else { - ("200 OK", "Authorization was not completed. You can close this tab.") + ( + "200 OK", + "Authorization was not completed. You can close this tab.", + ) }; let _ = respond(&mut stream, status, page).await; @@ -197,7 +206,9 @@ mod tests { // A stray request first — must not terminate the wait. let mut s = tokio::net::TcpStream::connect(addr).await.unwrap(); - s.write_all(b"GET /favicon.ico HTTP/1.1\r\n\r\n").await.unwrap(); + s.write_all(b"GET /favicon.ico HTTP/1.1\r\n\r\n") + .await + .unwrap(); let mut buf = Vec::new(); let _ = s.read_to_end(&mut buf).await; diff --git a/crates/auth/src/pkce.rs b/crates/auth/src/pkce.rs index 1e98dfa..f623906 100644 --- a/crates/auth/src/pkce.rs +++ b/crates/auth/src/pkce.rs @@ -16,7 +16,10 @@ impl Pkce { getrandom::fill(&mut bytes).expect("os rng"); let verifier = URL_SAFE_NO_PAD.encode(bytes); let challenge = challenge_s256(&verifier); - Self { verifier, challenge } + Self { + verifier, + challenge, + } } } diff --git a/crates/cli/src/agents.rs b/crates/cli/src/agents.rs index 4e2b955..87dc196 100644 --- a/crates/cli/src/agents.rs +++ b/crates/cli/src/agents.rs @@ -219,11 +219,11 @@ fn draw_header(f: &mut Frame, area: Rect, app: &App) { .filter(|r| r.status == AgentStatus::Working) .count(); let line = Line::from(vec![ - Span::styled(" bullpen agents ", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(format!( - "· {} sessions · {working} working", - app.rows.len() - )), + Span::styled( + " bullpen agents ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(format!("· {} sessions · {working} working", app.rows.len())), ]); f.render_widget(Paragraph::new(line), area); } @@ -245,9 +245,17 @@ fn draw_list(f: &mut Frame, area: Rect, app: &App) { let selected = i == app.selected; let marker = if selected { "› " } else { " " }; let s = &row.session; - let title = if s.title.is_empty() { "(untitled)" } else { &s.title }; + let title = if s.title.is_empty() { + "(untitled)" + } else { + &s.title + }; let title: String = title.chars().take(48).collect(); - let child = if s.parent_session_id.is_some() { " ↳" } else { "" }; + let child = if s.parent_session_id.is_some() { + " ↳" + } else { + "" + }; let text = format!( "{marker}{} {:<10} {:>6}/{:<6} {title}{child}", &s.id[..8], @@ -280,10 +288,7 @@ fn draw_input(f: &mut Frame, area: Rect, app: &App) { }; let block = Block::default().borders(Borders::ALL).title(title); let content = if app.input.is_empty() { - Span::styled( - "describe a task…", - Style::default().fg(Color::DarkGray), - ) + Span::styled("describe a task…", Style::default().fg(Color::DarkGray)) } else { Span::raw(app.input.as_str()) }; @@ -330,7 +335,9 @@ fn draw_peek(f: &mut Frame, app: &App) { .borders(Borders::ALL) .title(" peek · Esc to close "); f.render_widget( - Paragraph::new(lines).block(block).wrap(Wrap { trim: false }), + Paragraph::new(lines) + .block(block) + .wrap(Wrap { trim: false }), area, ); } @@ -402,7 +409,7 @@ mod tests { vec![ // Working, newest first "work-b", "work-a", // then Failed - "fail", // then Completed, newest first + "fail", // then Completed, newest first "done-new", "done-old", ] ); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 71736d6..91721b8 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -159,8 +159,7 @@ enum LoginProvider { async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "warn".into()), + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into()), ) .with_writer(std::io::stderr) .init(); @@ -379,19 +378,15 @@ async fn run( let (session, provider_kind, model) = match &resume { Some(prefix) => { let session = store.resolve_session(prefix)?; - let kind = ProviderKind::from_name(&session.provider).with_context(|| { - format!("session uses unknown provider `{}`", session.provider) - })?; + let kind = ProviderKind::from_name(&session.provider) + .with_context(|| format!("session uses unknown provider `{}`", session.provider))?; let model = session.model.clone(); (session, kind, model) } None => { let model = model.unwrap_or_else(|| provider_kind.default_model()); - let session = store.create_session( - &cwd.display().to_string(), - provider_kind.name(), - &model, - )?; + let session = + store.create_session(&cwd.display().to_string(), provider_kind.name(), &model)?; (session, provider_kind, model) } }; @@ -432,9 +427,7 @@ async fn run( eprintln!("✗ {name} failed") } // Assistant text streams to stdout via the delta sink below. - Event::AssistantText { .. } - | Event::ToolEnd { .. } - | Event::TurnDone { .. } => {} + Event::AssistantText { .. } | Event::ToolEnd { .. } | Event::TurnDone { .. } => {} } } }); @@ -457,10 +450,8 @@ async fn run( // The journal persists every step of the run as it happens (its own // store handle; WAL makes the two connections safe). If the process // dies mid-run, the next invocation recovers from the durable state. - let journal = bullpen_harness::StoreJournal::new( - Store::open(&Store::default_path())?, - &session.id, - ); + let journal = + bullpen_harness::StoreJournal::new(Store::open(&Store::default_path())?, &session.id); // The pen: the model can delegate bounded tasks to durable child agents // (sessions in the same store, resumable and listed like any other). let mut pen_config = bullpen_harness::PenConfig::new( @@ -494,7 +485,11 @@ async fn run( let _ = streamer.await; // Record the terminal run status for the dashboard. - let final_status = if result.is_ok() { "completed" } else { "failed" }; + let final_status = if result.is_ok() { + "completed" + } else { + "failed" + }; if let Ok(store) = Store::open(&Store::default_path()) { let _ = store.set_run_status(&session.id, final_status, None); } @@ -554,7 +549,11 @@ fn sessions() -> anyhow::Result<()> { s.provider, s.usage.input_tokens, s.usage.output_tokens, - if s.title.is_empty() { "(untitled)" } else { &s.title }, + if s.title.is_empty() { + "(untitled)" + } else { + &s.title + }, child_marker, ); } diff --git a/crates/harness/src/lib.rs b/crates/harness/src/lib.rs index 89d195f..bba9c27 100644 --- a/crates/harness/src/lib.rs +++ b/crates/harness/src/lib.rs @@ -155,7 +155,11 @@ impl Journal for StoreJournal { .map_err(jerr) } - async fn run_finished(&mut self, outcome: RunOutcome, usage: Usage) -> Result<(), JournalError> { + async fn run_finished( + &mut self, + outcome: RunOutcome, + usage: Usage, + ) -> Result<(), JournalError> { let sid = self.session_id.clone(); let run_id = self.run_id()?.to_string(); self.store() @@ -223,7 +227,12 @@ mod tests { input_schema: json!({"type": "object"}), } } - async fn run(&self, _: &ToolCtx, _: &str, input: Value) -> Result { + async fn run( + &self, + _: &ToolCtx, + _: &str, + input: Value, + ) -> Result { Ok(format!("echo: {}", input["value"])) } } @@ -275,7 +284,9 @@ mod tests { StopReason::ToolUse, ), response( - vec![ContentBlock::Text { text: "done".into() }], + vec![ContentBlock::Text { + text: "done".into(), + }], StopReason::EndTurn, ), ]); @@ -296,14 +307,19 @@ mod tests { // A follow-up run seeded from the durable transcript works. let provider = FakeProvider::new(vec![response( - vec![ContentBlock::Text { text: "again".into() }], + vec![ContentBlock::Text { + text: "again".into(), + }], StopReason::EndTurn, )]); let (messages, _) = prepare_session(&mut open_store(&dir), &session.id).unwrap(); let mut agent = agent_with(open_store(&dir), &session.id, provider); agent = agent.with_transcript(messages, session.usage); assert_eq!(agent.send("more").await.unwrap(), "again"); - assert_eq!(open_store(&dir).path_messages(&session.id).unwrap().len(), 6); + assert_eq!( + open_store(&dir).path_messages(&session.id).unwrap().len(), + 6 + ); } #[tokio::test] @@ -397,7 +413,11 @@ mod tests { let (messages, recovery) = prepare_session(&mut store, &session.id).unwrap(); let recovery = recovery.expect("recovery ran"); assert_eq!(recovery.interrupted_tools, 1); - let ContentBlock::ToolResult { tool_use_id, is_error, .. } = &messages[2].content[0] + let ContentBlock::ToolResult { + tool_use_id, + is_error, + .. + } = &messages[2].content[0] else { panic!("expected synthetic result, got {:?}", messages[2]); }; diff --git a/crates/harness/src/pen.rs b/crates/harness/src/pen.rs index 2b59d1d..c1c39e3 100644 --- a/crates/harness/src/pen.rs +++ b/crates/harness/src/pen.rs @@ -80,7 +80,11 @@ pub struct PenTool { } impl PenTool { - pub fn new(provider: Arc, parent_session: impl Into, config: PenConfig) -> Self { + pub fn new( + provider: Arc, + parent_session: impl Into, + config: PenConfig, + ) -> Self { Self { provider, parent_session: parent_session.into(), @@ -155,15 +159,21 @@ impl Tool for PenTool { // only to its own child session (WAL handles the store contention), // so they can run alongside each other. Work children mutate the // workspace and stay serial. - input.get("mode").and_then(Value::as_str).unwrap_or("inspect") == "inspect" + input + .get("mode") + .and_then(Value::as_str) + .unwrap_or("inspect") + == "inspect" } async fn run(&self, _ctx: &ToolCtx, call_id: &str, input: Value) -> Result { - let prompt = input - .get("prompt") + let prompt = input.get("prompt").and_then(Value::as_str).ok_or_else(|| { + ToolError::InvalidInput("missing required string field `prompt`".into()) + })?; + let mode = input + .get("mode") .and_then(Value::as_str) - .ok_or_else(|| ToolError::InvalidInput("missing required string field `prompt`".into()))?; - let mode = input.get("mode").and_then(Value::as_str).unwrap_or("inspect"); + .unwrap_or("inspect"); let registry = registry_for_mode(mode)?; let child_id = child_session_id(&self.parent_session, call_id); @@ -274,7 +284,11 @@ impl Tool for PenTool { "{answer}\n\n[child {short} · mode {mode} · {} in / {} out tokens{}]", usage.input_tokens, usage.output_tokens, - if recovery.is_some() { " · recovered" } else { "" }, + if recovery.is_some() { + " · recovered" + } else { + "" + }, )), Ok(Err(e)) => Err(ToolError::Failed(format!( "child {short} failed: {e} (session is saved and resumable)" @@ -351,9 +365,14 @@ mod tests { let store = Store::open(&dir.path().join("t.db")).unwrap(); assert_eq!(store.count_children(&parent).unwrap(), 1); - let child = store.get_session(&child_session_id(&parent, "call_1")).unwrap(); + let child = store + .get_session(&child_session_id(&parent, "call_1")) + .unwrap(); assert_eq!(child.parent_session_id.as_deref(), Some(parent.as_str())); - assert_eq!(store.last_run_outcome(&child.id).unwrap().as_deref(), Some("completed")); + assert_eq!( + store.last_run_outcome(&child.id).unwrap().as_deref(), + Some("completed") + ); } #[tokio::test] @@ -425,7 +444,8 @@ mod tests { &child_id, "e-user", "message", - &serde_json::to_value(bullpen_llm::Message::user_text("original task")).unwrap(), + &serde_json::to_value(bullpen_llm::Message::user_text("original task")) + .unwrap(), ) .unwrap(); } @@ -447,7 +467,10 @@ mod tests { let store = Store::open(&dir.path().join("t.db")).unwrap(); let messages = store.path_messages(&child_id).unwrap(); assert!(messages.iter().any(|m| m.text().contains("interrupted"))); - assert_eq!(store.last_run_outcome(&child_id).unwrap().as_deref(), Some("completed")); + assert_eq!( + store.last_run_outcome(&child_id).unwrap().as_deref(), + Some("completed") + ); } #[tokio::test] @@ -471,7 +494,11 @@ mod tests { ); let out = pen - .run(&tool_ctx(), "call_1", json!({"prompt": "task", "mode": "inspect"})) + .run( + &tool_ctx(), + "call_1", + json!({"prompt": "task", "mode": "inspect"}), + ) .await .unwrap(); assert!(out.contains("could not run that"), "{out}"); @@ -480,13 +507,10 @@ mod tests { let child_id = child_session_id(&parent, "call_1"); let messages = store.path_messages(&child_id).unwrap(); // The bash attempt got an unknown-tool error result, not execution. - assert!( - messages.iter().any(|m| m - .content - .iter() - .any(|b| matches!(b, ContentBlock::ToolResult { content, is_error: true, .. } - if content.contains("unknown tool")))), - ); + assert!(messages.iter().any(|m| m.content.iter().any( + |b| matches!(b, ContentBlock::ToolResult { content, is_error: true, .. } + if content.contains("unknown tool")) + )),); } #[tokio::test] diff --git a/crates/llm/src/anthropic.rs b/crates/llm/src/anthropic.rs index 1a07e43..2097ee1 100644 --- a/crates/llm/src/anthropic.rs +++ b/crates/llm/src/anthropic.rs @@ -265,7 +265,11 @@ struct StreamAccumulator { enum StreamBlock { Text(String), - ToolUse { id: String, name: String, json: String }, + ToolUse { + id: String, + name: String, + json: String, + }, Ignored, } @@ -309,7 +313,8 @@ impl StreamAccumulator { let delta = event.get("delta"); match delta.and_then(|d| d.get("type")).and_then(Value::as_str) { Some("text_delta") => { - if let Some(text) = delta.and_then(|d| d.get("text")).and_then(Value::as_str) + if let Some(text) = + delta.and_then(|d| d.get("text")).and_then(Value::as_str) { let _ = deltas.send(text.to_string()); if let Some(StreamBlock::Text(acc)) = self.blocks.get_mut(idx) { @@ -577,7 +582,9 @@ mod tests { Message { role: Role::Assistant, content: vec![ - ContentBlock::Text { text: "checking".into() }, + ContentBlock::Text { + text: "checking".into(), + }, ContentBlock::ToolUse { id: "tu_1".into(), name: "bash".into(), @@ -672,7 +679,13 @@ mod tests { assert_eq!(kimi.name(), "kimi"); assert_eq!(kimi.auth, AuthStyle::Bearer); // Both produce identical wire bodies to Anthropic proper. - assert_eq!(to_wire(&sample_request(), false)["messages"].as_array().unwrap().len(), 3); + assert_eq!( + to_wire(&sample_request(), false)["messages"] + .as_array() + .unwrap() + .len(), + 3 + ); } #[test] diff --git a/crates/llm/src/chatcompletions.rs b/crates/llm/src/chatcompletions.rs index 5be53f3..ae4fd79 100644 --- a/crates/llm/src/chatcompletions.rs +++ b/crates/llm/src/chatcompletions.rs @@ -350,7 +350,9 @@ mod tests { Message { role: Role::Assistant, content: vec![ - ContentBlock::Text { text: "checking".into() }, + ContentBlock::Text { + text: "checking".into(), + }, ContentBlock::ToolUse { id: "call_1".into(), name: "bash".into(), @@ -370,7 +372,9 @@ mod tests { content: "README.md".into(), is_error: false, }, - ContentBlock::Text { text: "now continue".into() }, + ContentBlock::Text { + text: "now continue".into(), + }, ], }, ], @@ -445,7 +449,12 @@ mod tests { .unwrap(); let resp = from_wire(wire).unwrap(); assert_eq!(resp.stop_reason, StopReason::EndTurn); - assert_eq!(resp.content, vec![ContentBlock::Text { text: "hello".into() }]); + assert_eq!( + resp.content, + vec![ContentBlock::Text { + text: "hello".into() + }] + ); } #[test] diff --git a/crates/llm/src/codex.rs b/crates/llm/src/codex.rs index 78797a6..2681342 100644 --- a/crates/llm/src/codex.rs +++ b/crates/llm/src/codex.rs @@ -19,9 +19,7 @@ use serde_json::{Map, Value, json}; use crate::retry; use crate::sse; -use crate::{ - ContentBlock, Provider, ProviderError, Request, Response, Role, StopReason, Usage, -}; +use crate::{ContentBlock, Provider, ProviderError, Request, Response, Role, StopReason, Usage}; const CODEX_ENDPOINT: &str = "https://chatgpt.com/backend-api/codex/responses"; /// Fallback when neither `-m` nor a Codex CLI config supplies a model. @@ -249,7 +247,11 @@ fn to_input(req: &Request) -> Vec { /// replay: type "reasoning", non-empty id and encrypted_content, and a /// summary that is either absent or a real array. fn replayable_reasoning(data: &Value) -> bool { - let nonempty = |key: &str| data.get(key).and_then(Value::as_str).is_some_and(|s| !s.is_empty()); + let nonempty = |key: &str| { + data.get(key) + .and_then(Value::as_str) + .is_some_and(|s| !s.is_empty()) + }; if data.get("type").and_then(Value::as_str) != Some("reasoning") || !nonempty("id") || !nonempty("encrypted_content") @@ -335,11 +337,21 @@ fn to_response(body: &Value) -> Result { let mut content = Vec::new(); let mut saw_tool_call = false; - for item in body.get("output").and_then(Value::as_array).into_iter().flatten() { + for item in body + .get("output") + .and_then(Value::as_array) + .into_iter() + .flatten() + { match item.get("type").and_then(Value::as_str).unwrap_or("") { "message" => { let mut text = String::new(); - for part in item.get("content").and_then(Value::as_array).into_iter().flatten() { + for part in item + .get("content") + .and_then(Value::as_array) + .into_iter() + .flatten() + { match part.get("type").and_then(Value::as_str).unwrap_or("") { "output_text" => { text.push_str(part.get("text").and_then(Value::as_str).unwrap_or("")) @@ -445,7 +457,9 @@ mod tests { "summary": [] }), }, - ContentBlock::Text { text: "running".into() }, + ContentBlock::Text { + text: "running".into(), + }, ContentBlock::ToolUse { id: "call_1".into(), name: "bash".into(), @@ -499,7 +513,13 @@ mod tests { data: json!({"type": "reasoning", "id": "x", "encrypted_content": "y"}), }; let wire = build_request(&req); - assert!(!wire["input"].as_array().unwrap().iter().any(|i| i["type"] == "reasoning")); + assert!( + !wire["input"] + .as_array() + .unwrap() + .iter() + .any(|i| i["type"] == "reasoning") + ); // summary present but not an array → not replayable assert!(!replayable_reasoning(&json!({ @@ -524,7 +544,9 @@ mod tests { assert_eq!(resp.stop_reason, StopReason::ToolUse); assert_eq!(resp.usage.input_tokens, 11); assert_eq!(resp.content.len(), 3); - assert!(matches!(&resp.content[0], ContentBlock::Opaque { provider, .. } if provider == "codex")); + assert!( + matches!(&resp.content[0], ContentBlock::Opaque { provider, .. } if provider == "codex") + ); assert!(matches!(&resp.content[1], ContentBlock::Text { text } if text == "checking")); let ContentBlock::ToolUse { id, name, input } = &resp.content[2] else { panic!("expected tool use"); diff --git a/crates/llm/src/lib.rs b/crates/llm/src/lib.rs index 783e3b5..78ddfc0 100644 --- a/crates/llm/src/lib.rs +++ b/crates/llm/src/lib.rs @@ -137,9 +137,7 @@ pub struct Response { impl Response { pub fn tool_uses(&self) -> impl Iterator { self.content.iter().filter_map(|b| match b { - ContentBlock::ToolUse { id, name, input } => { - Some((id.as_str(), name.as_str(), input)) - } + ContentBlock::ToolUse { id, name, input } => Some((id.as_str(), name.as_str(), input)), _ => None, }) } diff --git a/crates/sandbox/src/lib.rs b/crates/sandbox/src/lib.rs index 499e4f7..32ffdf6 100644 --- a/crates/sandbox/src/lib.rs +++ b/crates/sandbox/src/lib.rs @@ -97,7 +97,10 @@ impl Sandbox { ], ) } else { - ("bash".to_string(), vec!["-c".to_string(), command.to_string()]) + ( + "bash".to_string(), + vec!["-c".to_string(), command.to_string()], + ) } } @@ -143,7 +146,9 @@ fn resolve_for_write(path: &Path) -> PathBuf { let mut tail = PathBuf::new(); loop { if existing.exists() { - let base = existing.canonicalize().unwrap_or_else(|_| existing.to_path_buf()); + let base = existing + .canonicalize() + .unwrap_or_else(|_| existing.to_path_buf()); return base.join(&tail); } match (existing.parent(), existing.file_name()) { @@ -225,9 +230,11 @@ mod tests { assert!(profile.contains(&canonical.to_string_lossy().to_string())); // Non-strict allows network (no deny line). - assert!(!Sandbox::workspace(dir.path()) - .seatbelt_profile() - .contains("(deny network*)")); + assert!( + !Sandbox::workspace(dir.path()) + .seatbelt_profile() + .contains("(deny network*)") + ); } #[test] diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index ee6bf72..22d0f4e 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -121,9 +121,9 @@ impl Store { } fn migrate(&mut self) -> Result<(), StoreError> { - let version: i64 = - self.conn - .query_row("SELECT * FROM pragma_user_version", [], |r| r.get(0))?; + let version: i64 = self + .conn + .query_row("SELECT * FROM pragma_user_version", [], |r| r.get(0))?; if version < 1 { let tx = self.conn.transaction()?; tx.execute_batch( @@ -404,7 +404,14 @@ impl Store { tx.execute( "INSERT INTO entries (id, session_id, parent_id, seq, kind, payload) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - params![provisioned_id, session_id, parent, seq, kind, payload.to_string()], + params![ + provisioned_id, + session_id, + parent, + seq, + kind, + payload.to_string() + ], )?; tx.execute( "UPDATE lanes SET leaf_id = ?3 WHERE session_id = ?1 AND name = ?2", @@ -448,8 +455,7 @@ impl Store { parent_id: row.get(1)?, seq: row.get(2)?, kind: row.get(3)?, - payload: serde_json::from_str(&row.get::<_, String>(4)?) - .unwrap_or(Value::Null), + payload: serde_json::from_str(&row.get::<_, String>(4)?).unwrap_or(Value::Null), }) })?; for row in rows { @@ -460,9 +466,9 @@ impl Store { let mut path = Vec::new(); let mut cursor = self.leaf(session_id)?; while let Some(id) = cursor { - let entry = by_id - .remove(&id) - .ok_or_else(|| StoreError::Corrupt(format!("leaf path references missing entry {id}")))?; + let entry = by_id.remove(&id).ok_or_else(|| { + StoreError::Corrupt(format!("leaf path references missing entry {id}")) + })?; cursor = entry.parent_id.clone(); path.push(entry); } @@ -503,7 +509,15 @@ impl Store { tx.execute( "INSERT INTO records (id, session_id, lane, run_id, seq, kind, payload) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", - params![id, session_id, MAIN_LANE, run_id, seq, kind, payload.to_string()], + params![ + id, + session_id, + MAIN_LANE, + run_id, + seq, + kind, + payload.to_string() + ], )?; } tx.commit()?; @@ -528,7 +542,13 @@ impl Store { Some(id) => Value::String(id), None => Value::Null, }; - self.append_record(session_id, &run_id.clone(), &run_id, "operation_started", &payload)?; + self.append_record( + session_id, + &run_id.clone(), + &run_id, + "operation_started", + &payload, + )?; self.conn.execute( "UPDATE lanes SET open_operation_id = ?3 WHERE session_id = ?1 AND name = ?2", params![session_id, MAIN_LANE, run_id], @@ -715,8 +735,12 @@ mod tests { role: Role::Assistant, content: vec![ContentBlock::Text { text: "hi".into() }], }; - store.append_entry(&s.id, "e1", "message", &msg_payload(&m1)).unwrap(); - store.append_entry(&s.id, "e2", "message", &msg_payload(&m2)).unwrap(); + store + .append_entry(&s.id, "e1", "message", &msg_payload(&m1)) + .unwrap(); + store + .append_entry(&s.id, "e2", "message", &msg_payload(&m2)) + .unwrap(); assert_eq!(store.leaf(&s.id).unwrap().as_deref(), Some("e2")); let messages = store.path_messages(&s.id).unwrap(); @@ -728,8 +752,12 @@ mod tests { let (_dir, mut store) = store(); let s = store.create_session("/tmp", "anthropic", "m").unwrap(); let payload = msg_payload(&Message::user_text("once")); - store.append_entry(&s.id, "e1", "message", &payload).unwrap(); - store.append_entry(&s.id, "e1", "message", &payload).unwrap(); + store + .append_entry(&s.id, "e1", "message", &payload) + .unwrap(); + store + .append_entry(&s.id, "e1", "message", &payload) + .unwrap(); assert_eq!(store.path(&s.id).unwrap().len(), 1); store @@ -751,7 +779,9 @@ mod tests { let s = store.create_session("/tmp", "anthropic", "m").unwrap(); assert!(store.open_run(&s.id).unwrap().is_none()); - let run = store.start_operation(&s.id, &json!({"prompt": "go"})).unwrap(); + let run = store + .start_operation(&s.id, &json!({"prompt": "go"})) + .unwrap(); let open = store.open_run(&s.id).unwrap().expect("suspended"); assert_eq!(open.run_id, run); assert_eq!(open.records.len(), 1); @@ -773,11 +803,21 @@ mod tests { let (_dir, mut store) = store(); let s = store.create_session("/tmp", "anthropic", "m").unwrap(); store - .append_entry(&s.id, "e1", "message", &msg_payload(&Message::user_text("a"))) + .append_entry( + &s.id, + "e1", + "message", + &msg_payload(&Message::user_text("a")), + ) .unwrap(); let run = store.start_operation(&s.id, &json!({})).unwrap(); store - .append_entry(&s.id, "e2", "message", &msg_payload(&Message::user_text("b"))) + .append_entry( + &s.id, + "e2", + "message", + &msg_payload(&Message::user_text("b")), + ) .unwrap(); let entry_seqs: Vec = store.path(&s.id).unwrap().iter().map(|e| e.seq).collect(); diff --git a/crates/store/src/recovery.rs b/crates/store/src/recovery.rs index 9b6ec0e..2fd3f0c 100644 --- a/crates/store/src/recovery.rs +++ b/crates/store/src/recovery.rs @@ -130,7 +130,12 @@ mod tests { let s = store.create_session("/tmp", "anthropic", "m").unwrap(); let run = store.start_operation(&s.id, &json!({})).unwrap(); store - .append_entry(&s.id, "e-user", "message", &payload(&Message::user_text("go"))) + .append_entry( + &s.id, + "e-user", + "message", + &payload(&Message::user_text("go")), + ) .unwrap(); let assistant = Message { role: Role::Assistant, @@ -166,7 +171,9 @@ mod tests { let (_dir, mut store) = store(); let (session, run) = crashed_mid_batch(&mut store); - let recovery = recover(&mut store, &session).unwrap().expect("suspended run"); + let recovery = recover(&mut store, &session) + .unwrap() + .expect("suspended run"); assert_eq!(recovery.run_id, run); assert_eq!(recovery.interrupted_tools, 1); assert!(recovery.closed_with_note); @@ -175,7 +182,11 @@ mod tests { // interrupted result, then a closing assistant message. let messages = store.path_messages(&session).unwrap(); assert_eq!(messages.len(), 4); - let ContentBlock::ToolResult { tool_use_id, is_error, content } = &messages[2].content[0] + let ContentBlock::ToolResult { + tool_use_id, + is_error, + content, + } = &messages[2].content[0] else { panic!("expected synthetic tool result"); }; @@ -220,7 +231,10 @@ mod tests { // Real result survives untouched; closing note still appended // because the leaf ended on a user(tool-results) message. let messages = store.path_messages(&session).unwrap(); - let ContentBlock::ToolResult { content, is_error, .. } = &messages[2].content[0] else { + let ContentBlock::ToolResult { + content, is_error, .. + } = &messages[2].content[0] + else { panic!("expected result"); }; assert_eq!(content, "deployed"); diff --git a/crates/tools/src/bash.rs b/crates/tools/src/bash.rs index 27c3747..cc626b5 100644 --- a/crates/tools/src/bash.rs +++ b/crates/tools/src/bash.rs @@ -54,7 +54,10 @@ impl Tool for Bash { // workspace via the OS mechanism; otherwise plain bash. let (program, args) = match &ctx.sandbox { Some(sb) => sb.wrap_bash(command), - None => ("bash".to_string(), vec!["-c".to_string(), command.to_string()]), + None => ( + "bash".to_string(), + vec!["-c".to_string(), command.to_string()], + ), }; let child = tokio::process::Command::new(program) .args(args) @@ -108,7 +111,10 @@ mod tests { #[tokio::test] async fn runs_and_captures_stdout() { - let out = Bash.run(&ctx(), "t", json!({"command": "echo hello"})).await.unwrap(); + let out = Bash + .run(&ctx(), "t", json!({"command": "echo hello"})) + .await + .unwrap(); assert_eq!(out.trim(), "hello"); } @@ -126,7 +132,11 @@ mod tests { #[tokio::test] async fn times_out() { let err = Bash - .run(&ctx(), "t", json!({"command": "sleep 5", "timeout_seconds": 1})) + .run( + &ctx(), + "t", + json!({"command": "sleep 5", "timeout_seconds": 1}), + ) .await .unwrap_err(); assert!(matches!(err, ToolError::Timeout(1))); diff --git a/crates/tools/src/fs.rs b/crates/tools/src/fs.rs index 1ec73e8..37b22a5 100644 --- a/crates/tools/src/fs.rs +++ b/crates/tools/src/fs.rs @@ -48,7 +48,11 @@ impl Tool for ReadFile { .map_err(|e| ToolError::Failed(format!("cannot read {}: {e}", path.display())))?; let text = String::from_utf8_lossy(&raw); - let offset = input.get("offset").and_then(Value::as_u64).unwrap_or(1).max(1) as usize; + let offset = input + .get("offset") + .and_then(Value::as_u64) + .unwrap_or(1) + .max(1) as usize; let limit = input .get("limit") .and_then(Value::as_u64) @@ -63,7 +67,10 @@ impl Tool for ReadFile { .collect(); if numbered.is_empty() { - return Ok(format!("(empty selection: file has {} lines)", text.lines().count())); + return Ok(format!( + "(empty selection: file has {} lines)", + text.lines().count() + )); } Ok(truncate_middle(numbered, MAX_READ_BYTES)) } @@ -99,14 +106,18 @@ impl Tool for WriteFile { let content = required_str(&input, "content")?; ctx.check_write(&path)?; if let Some(parent) = path.parent() { - tokio::fs::create_dir_all(parent) - .await - .map_err(|e| ToolError::Failed(format!("cannot create {}: {e}", parent.display())))?; + tokio::fs::create_dir_all(parent).await.map_err(|e| { + ToolError::Failed(format!("cannot create {}: {e}", parent.display())) + })?; } tokio::fs::write(&path, content) .await .map_err(|e| ToolError::Failed(format!("cannot write {}: {e}", path.display())))?; - Ok(format!("wrote {} bytes to {}", content.len(), path.display())) + Ok(format!( + "wrote {} bytes to {}", + content.len(), + path.display() + )) } } @@ -141,7 +152,9 @@ impl Tool for EditFile { let old = required_str(&input, "old_string")?; let new = required_str(&input, "new_string")?; if old.is_empty() { - return Err(ToolError::InvalidInput("old_string must not be empty".into())); + return Err(ToolError::InvalidInput( + "old_string must not be empty".into(), + )); } ctx.check_write(&path)?; @@ -156,9 +169,9 @@ impl Tool for EditFile { ))), 1 => { let updated = text.replacen(old, new, 1); - tokio::fs::write(&path, updated) - .await - .map_err(|e| ToolError::Failed(format!("cannot write {}: {e}", path.display())))?; + tokio::fs::write(&path, updated).await.map_err(|e| { + ToolError::Failed(format!("cannot write {}: {e}", path.display())) + })?; Ok(format!("edited {}", path.display())) } n => Err(ToolError::Failed(format!( @@ -182,10 +195,17 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let c = ctx(&dir); WriteFile - .run(&c, "t", json!({"path": "sub/f.txt", "content": "one\ntwo\nthree"})) + .run( + &c, + "t", + json!({"path": "sub/f.txt", "content": "one\ntwo\nthree"}), + ) + .await + .unwrap(); + let out = ReadFile + .run(&c, "t", json!({"path": "sub/f.txt"})) .await .unwrap(); - let out = ReadFile.run(&c, "t", json!({"path": "sub/f.txt"})).await.unwrap(); assert_eq!(out, "1\tone\n2\ttwo\n3\tthree\n"); } @@ -209,11 +229,19 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let c = ctx(&dir); WriteFile - .run(&c, "t", json!({"path": "f.txt", "content": "x = 1\nx = 1\n"})) + .run( + &c, + "t", + json!({"path": "f.txt", "content": "x = 1\nx = 1\n"}), + ) .await .unwrap(); let err = EditFile - .run(&c, "t", json!({"path": "f.txt", "old_string": "x = 1", "new_string": "x = 2"})) + .run( + &c, + "t", + json!({"path": "f.txt", "old_string": "x = 1", "new_string": "x = 2"}), + ) .await .unwrap_err(); assert!(err.to_string().contains("2 times")); @@ -222,7 +250,10 @@ mod tests { .run(&c, "t", json!({"path": "f.txt", "old_string": "x = 1\nx = 1", "new_string": "x = 2\nx = 1"})) .await .unwrap(); - let out = ReadFile.run(&c, "t", json!({"path": "f.txt"})).await.unwrap(); + let out = ReadFile + .run(&c, "t", json!({"path": "f.txt"})) + .await + .unwrap(); assert!(out.contains("x = 2")); } @@ -240,7 +271,11 @@ mod tests { // Absolute path outside the workspace: refused in-process. let err = WriteFile - .run(&c, "t", json!({"path": "/etc/bullpen-escape", "content": "x"})) + .run( + &c, + "t", + json!({"path": "/etc/bullpen-escape", "content": "x"}), + ) .await .unwrap_err(); assert!(err.to_string().contains("sandbox"), "{err}"); @@ -255,7 +290,11 @@ mod tests { .await .unwrap(); let err = EditFile - .run(&c, "t", json!({"path": "f.txt", "old_string": "absent", "new_string": "y"})) + .run( + &c, + "t", + json!({"path": "f.txt", "old_string": "absent", "new_string": "y"}), + ) .await .unwrap_err(); assert!(err.to_string().contains("not found")); diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 118f0bd..4727d16 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -176,7 +176,14 @@ mod tests { assert_eq!(names, sorted); assert_eq!( names, - vec!["bash", "edit_file", "glob", "grep", "read_file", "write_file"] + vec![ + "bash", + "edit_file", + "glob", + "grep", + "read_file", + "write_file" + ] ); } diff --git a/crates/tools/src/search.rs b/crates/tools/src/search.rs index dc152c4..0aeaa97 100644 --- a/crates/tools/src/search.rs +++ b/crates/tools/src/search.rs @@ -142,7 +142,10 @@ impl Tool for Glob { if !entry.file_type().is_some_and(|t| t.is_file()) { continue; } - let rel = entry.path().strip_prefix(&workspace).unwrap_or(entry.path()); + let rel = entry + .path() + .strip_prefix(&workspace) + .unwrap_or(entry.path()); if glob.is_match(rel) { hits.push(rel.display().to_string()); if hits.len() >= MAX_RESULTS { @@ -182,7 +185,10 @@ mod tests { #[tokio::test] async fn grep_finds_matches_with_locations() { let (_dir, ctx) = fixture().await; - let out = Grep.run(&ctx, "t", json!({"pattern": "steel"})).await.unwrap(); + let out = Grep + .run(&ctx, "t", json!({"pattern": "steel"})) + .await + .unwrap(); assert!(out.contains("src/main.rs:2:"), "{out}"); assert!(out.contains("notes.md:1:"), "{out}"); } @@ -190,16 +196,25 @@ mod tests { #[tokio::test] async fn grep_rejects_bad_regex() { let (_dir, ctx) = fixture().await; - let err = Grep.run(&ctx, "t", json!({"pattern": "["})).await.unwrap_err(); + let err = Grep + .run(&ctx, "t", json!({"pattern": "["})) + .await + .unwrap_err(); assert!(matches!(err, ToolError::InvalidInput(_))); } #[tokio::test] async fn glob_matches_relative_paths() { let (_dir, ctx) = fixture().await; - let out = Glob.run(&ctx, "t", json!({"pattern": "**/*.rs"})).await.unwrap(); + let out = Glob + .run(&ctx, "t", json!({"pattern": "**/*.rs"})) + .await + .unwrap(); assert_eq!(out, "src/main.rs"); - let out = Glob.run(&ctx, "t", json!({"pattern": "*.md"})).await.unwrap(); + let out = Glob + .run(&ctx, "t", json!({"pattern": "*.md"})) + .await + .unwrap(); assert_eq!(out, "notes.md"); } } From 4c17fdb710e342d95ce5b4667d46ac2296841b9a Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Fri, 7 Aug 2026 23:21:28 -0700 Subject: [PATCH 2/3] ci: gate on cargo fmt --check Now that the tree is rustfmt-clean, enforce it. Without a gate, formatting drifts silently and every contributor's format-on-save produces diff noise unrelated to their change. Skips rust-cache deliberately: rustfmt parses sources and never builds, so there is nothing to restore and the cache round-trip would cost more than the job. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PfAfAujueuZ3rDTiL9apx3 --- .github/workflows/ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a75465..392b28c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,22 @@ jobs: - name: Test run: cargo test --workspace + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install pinned toolchain + run: rustup toolchain install --no-self-update + + # No rust-cache: rustfmt parses sources and never builds, so there is + # nothing to restore and the cache round-trip would cost more than the job. + - name: Format + run: cargo fmt --all --check + clippy: name: clippy runs-on: ubuntu-latest From b50ee68cdf8fb933a3d685ec0ddc4788197497d2 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Fri, 7 Aug 2026 23:24:53 -0700 Subject: [PATCH 3/3] ci: restrict the workflow token to contents: read Without a permissions block, every job inherits the repository default. That default is currently read, but it is a repo setting anyone can widen, and the workflow would silently follow it. Declared at workflow level rather than per-job so a job added later cannot quietly inherit something broader. CI only ever reads the repo. Found by zizmor (excessive-permissions) via CodeRabbit, which flagged the new fmt job; test and clippy had the same gap. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PfAfAujueuZ3rDTiL9apx3 --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 392b28c..66d2ef5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,11 @@ concurrency: env: CARGO_TERM_COLOR: always +# Least privilege for every job: CI only reads the repo. Declared here rather +# than per-job so a new job cannot silently inherit a wider repository default. +permissions: + contents: read + jobs: test: # Both platforms matter: bullpen-sandbox confines shell commands with