fix: normalize claude system events and refresh tests

This commit is contained in:
Nathan Flurry 2026-01-26 20:44:58 -08:00
parent fdeef51f9c
commit c91595d338
14 changed files with 99 additions and 25 deletions

View file

@ -112,11 +112,13 @@ impl AgentManager {
pub fn install(&self, agent: AgentId, options: InstallOptions) -> Result<InstallResult, AgentError> {
let install_path = self.binary_path(agent);
if install_path.exists() && !options.reinstall {
return Ok(InstallResult {
path: install_path,
version: self.version(agent).unwrap_or(None),
});
if !options.reinstall {
if let Ok(existing_path) = self.resolve_binary(agent) {
return Ok(InstallResult {
path: existing_path,
version: self.version(agent).unwrap_or(None),
});
}
}
fs::create_dir_all(&self.install_dir)?;
@ -135,7 +137,9 @@ impl AgentManager {
}
pub fn is_installed(&self, agent: AgentId) -> bool {
self.binary_path(agent).exists() || find_in_path(agent.binary_name()).is_some()
self.binary_path(agent).exists()
|| find_in_path(agent.binary_name()).is_some()
|| default_install_dir().join(agent.binary_name()).exists()
}
pub fn binary_path(&self, agent: AgentId) -> PathBuf {
@ -368,6 +372,10 @@ impl AgentManager {
if let Some(path) = find_in_path(agent.binary_name()) {
return Ok(path);
}
let fallback = default_install_dir().join(agent.binary_name());
if fallback.exists() {
return Ok(fallback);
}
Err(AgentError::BinaryNotFound { agent })
}
}
@ -780,6 +788,12 @@ fn find_in_path(binary_name: &str) -> Option<PathBuf> {
None
}
fn default_install_dir() -> PathBuf {
dirs::data_dir()
.map(|dir| dir.join("sandbox-agent").join("bin"))
.unwrap_or_else(|| PathBuf::from(".").join(".sandbox-agent").join("bin"))
}
fn download_bytes(url: &Url) -> Result<Vec<u8>, AgentError> {
let client = Client::builder().build()?;
let mut response = client.get(url.clone()).send()?;

View file

@ -19,7 +19,7 @@ use tower_http::cors::CorsLayer;
const PROMPT: &str = "Reply with exactly the single word OK.";
const PERMISSION_PROMPT: &str = "List files in the current directory using available tools.";
const QUESTION_PROMPT: &str =
"Ask the user a multiple-choice question with options yes/no using any built-in AskUserQuestion tool, then wait.";
"Use the AskUserQuestion tool to ask exactly one yes/no question, then wait for a reply. Do not answer yourself.";
struct TestApp {
app: Router,
@ -1022,7 +1022,7 @@ async fn approval_flow_snapshots() {
}
let question_reply_session = format!("question-reply-{}", config.agent.as_str());
create_session(&app.app, config.agent, &question_reply_session, test_permission_mode(config.agent)).await;
create_session(&app.app, config.agent, &question_reply_session, "plan").await;
let status = send_status(
&app.app,
Method::POST,
@ -1083,7 +1083,7 @@ async fn approval_flow_snapshots() {
}
let question_reject_session = format!("question-reject-{}", config.agent.as_str());
create_session(&app.app, config.agent, &question_reject_session, test_permission_mode(config.agent)).await;
create_session(&app.app, config.agent, &question_reject_session, "plan").await;
let status = send_status(
&app.app,
Method::POST,

View file

@ -1,6 +1,5 @@
---
source: server/packages/sandbox-agent/tests/http_sse_snapshots.rs
assertion_line: 978
expression: normalize_events(&permission_events)
---
- agent: claude
@ -9,8 +8,10 @@ expression: normalize_events(&permission_events)
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -1,6 +1,5 @@
---
source: server/packages/sandbox-agent/tests/http_sse_snapshots.rs
assertion_line: 1100
expression: normalize_events(&reject_events)
---
- agent: claude
@ -9,8 +8,10 @@ expression: normalize_events(&reject_events)
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -8,8 +8,10 @@ expression: normalize_events(&question_events)
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -1,6 +1,5 @@
---
source: server/packages/sandbox-agent/tests/http_sse_snapshots.rs
assertion_line: 1232
expression: snapshot
---
session_a:
@ -10,8 +9,10 @@ session_a:
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:
@ -27,8 +28,10 @@ session_b:
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -1,6 +1,5 @@
---
source: server/packages/sandbox-agent/tests/http_sse_snapshots.rs
assertion_line: 721
expression: normalized
---
- agent: claude
@ -9,8 +8,10 @@ expression: normalized
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -1,6 +1,5 @@
---
source: server/packages/sandbox-agent/tests/http_sse_snapshots.rs
assertion_line: 729
expression: normalized
---
- agent: claude
@ -9,8 +8,10 @@ expression: normalized
started:
message: session.created
- agent: claude
kind: unknown
kind: started
seq: 2
started:
message: system.init
- agent: claude
kind: message
message:

View file

@ -7,6 +7,7 @@ use crate::{
QuestionInfo,
QuestionOption,
QuestionRequest,
Started,
UniversalEventData,
UniversalMessage,
UniversalMessageParsed,
@ -20,6 +21,7 @@ pub fn event_to_universal_with_session(
) -> EventConversion {
let event_type = event.get("type").and_then(Value::as_str).unwrap_or("");
match event_type {
"system" => system_event_to_universal(event),
"assistant" => assistant_event_to_universal(event),
"tool_use" => tool_use_event_to_universal(event, session_id),
"tool_result" => tool_result_event_to_universal(event),
@ -114,6 +116,18 @@ fn assistant_event_to_universal(event: &Value) -> EventConversion {
EventConversion::new(UniversalEventData::Message { message })
}
fn system_event_to_universal(event: &Value) -> EventConversion {
let subtype = event
.get("subtype")
.and_then(Value::as_str)
.unwrap_or("system");
let started = Started {
message: Some(format!("system.{subtype}")),
details: Some(event.clone()),
};
EventConversion::new(UniversalEventData::Started { started })
}
fn tool_use_event_to_universal(event: &Value, session_id: String) -> EventConversion {
let tool_use = event.get("tool_use");
let name = tool_use