style: cargo fmt after parallel stream merges
This commit is contained in:
@@ -72,3 +72,4 @@ apps/
|
|||||||
# Claude Code runtime artifacts
|
# Claude Code runtime artifacts
|
||||||
.claude/scheduled_tasks.lock
|
.claude/scheduled_tasks.lock
|
||||||
.claude/worktrees/
|
.claude/worktrees/
|
||||||
|
.worktrees/
|
||||||
|
|||||||
@@ -3298,12 +3298,11 @@ impl Engine {
|
|||||||
"tool_id": tool_id.clone(),
|
"tool_id": tool_id.clone(),
|
||||||
"tool_name": tool_name.clone(),
|
"tool_name": tool_name.clone(),
|
||||||
}));
|
}));
|
||||||
let approval_key =
|
let approval_key = crate::tools::approval_cache::build_approval_key(
|
||||||
crate::tools::approval_cache::build_approval_key(
|
&tool_name,
|
||||||
&tool_name,
|
&tool_input,
|
||||||
&tool_input,
|
)
|
||||||
)
|
.0;
|
||||||
.0;
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.tx_event
|
.tx_event
|
||||||
.send(Event::ApprovalRequired {
|
.send(Event::ApprovalRequired {
|
||||||
|
|||||||
@@ -319,9 +319,7 @@ pub enum ClientError {
|
|||||||
retryable: bool,
|
retryable: bool,
|
||||||
},
|
},
|
||||||
/// Generic internal error that doesn't fit a provider taxonomy.
|
/// Generic internal error that doesn't fit a provider taxonomy.
|
||||||
Internal {
|
Internal { envelope: ErrorEnvelope },
|
||||||
envelope: ErrorEnvelope,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientError {
|
impl ClientError {
|
||||||
@@ -412,25 +410,15 @@ impl std::error::Error for ClientError {}
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum StreamError {
|
pub enum StreamError {
|
||||||
/// Stream stalled — no chunk received within the idle timeout.
|
/// Stream stalled — no chunk received within the idle timeout.
|
||||||
Stall {
|
Stall { timeout_secs: u64 },
|
||||||
timeout_secs: u64,
|
|
||||||
},
|
|
||||||
/// Chunk decode / JSON parse failure.
|
/// Chunk decode / JSON parse failure.
|
||||||
Decode {
|
Decode { message: String },
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
/// Stream exceeded content size limit.
|
/// Stream exceeded content size limit.
|
||||||
Overflow {
|
Overflow { limit_bytes: usize },
|
||||||
limit_bytes: usize,
|
|
||||||
},
|
|
||||||
/// Stream exceeded wall‑clock duration limit.
|
/// Stream exceeded wall‑clock duration limit.
|
||||||
DurationLimit {
|
DurationLimit { limit_secs: u64 },
|
||||||
limit_secs: u64,
|
|
||||||
},
|
|
||||||
/// Transport error from the underlying SSE connection.
|
/// Transport error from the underlying SSE connection.
|
||||||
Transport {
|
Transport { message: String },
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamError {
|
impl StreamError {
|
||||||
@@ -439,29 +427,19 @@ impl StreamError {
|
|||||||
pub fn into_client_error(self) -> ClientError {
|
pub fn into_client_error(self) -> ClientError {
|
||||||
match self {
|
match self {
|
||||||
Self::Stall { timeout_secs } => {
|
Self::Stall { timeout_secs } => {
|
||||||
ClientError::stream(
|
ClientError::stream(format!("Stream stalled after {timeout_secs}s idle"), true)
|
||||||
format!("Stream stalled after {timeout_secs}s idle"),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Self::Decode { message } => {
|
Self::Decode { message } => {
|
||||||
ClientError::stream(format!("Stream decode error: {message}"), true)
|
ClientError::stream(format!("Stream decode error: {message}"), true)
|
||||||
}
|
}
|
||||||
Self::Overflow { limit_bytes } => {
|
Self::Overflow { limit_bytes } => {
|
||||||
ClientError::stream(
|
ClientError::stream(format!("Stream exceeded {limit_bytes} bytes limit"), false)
|
||||||
format!("Stream exceeded {limit_bytes} bytes limit"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::DurationLimit { limit_secs } => {
|
|
||||||
ClientError::stream(
|
|
||||||
format!("Stream exceeded {limit_secs}s duration limit"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Transport { message } => {
|
|
||||||
ClientError::stream(message, true)
|
|
||||||
}
|
}
|
||||||
|
Self::DurationLimit { limit_secs } => ClientError::stream(
|
||||||
|
format!("Stream exceeded {limit_secs}s duration limit"),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
Self::Transport { message } => ClientError::stream(message, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,9 +134,8 @@ pub fn compose_prompt(mode: AppMode, personality: Personality) -> String {
|
|||||||
approval_prompt(mode).trim(),
|
approval_prompt(mode).trim(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut out = String::with_capacity(
|
let mut out =
|
||||||
parts.iter().map(|p| p.len()).sum::<usize>() + (parts.len() - 1) * 2,
|
String::with_capacity(parts.iter().map(|p| p.len()).sum::<usize>() + (parts.len() - 1) * 2);
|
||||||
);
|
|
||||||
for (i, part) in parts.iter().enumerate() {
|
for (i, part) in parts.iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
@@ -335,9 +334,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn each_mode_gets_correct_approval() {
|
fn each_mode_gets_correct_approval() {
|
||||||
assert!(compose_prompt(AppMode::Agent, Personality::Calm).contains("Approval Policy: Suggest"));
|
assert!(
|
||||||
|
compose_prompt(AppMode::Agent, Personality::Calm).contains("Approval Policy: Suggest")
|
||||||
|
);
|
||||||
assert!(compose_prompt(AppMode::Yolo, Personality::Calm).contains("Approval Policy: Auto"));
|
assert!(compose_prompt(AppMode::Yolo, Personality::Calm).contains("Approval Policy: Auto"));
|
||||||
assert!(compose_prompt(AppMode::Plan, Personality::Calm).contains("Approval Policy: Never"));
|
assert!(
|
||||||
|
compose_prompt(AppMode::Plan, Personality::Calm).contains("Approval Policy: Never")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -3177,7 +3177,8 @@ mod tests {
|
|||||||
|
|
||||||
harness
|
harness
|
||||||
.tx_event
|
.tx_event
|
||||||
.send(EngineEvent::ApprovalRequired { approval_key: "test_key".to_string(),
|
.send(EngineEvent::ApprovalRequired {
|
||||||
|
approval_key: "test_key".to_string(),
|
||||||
id: "tool_stale".to_string(),
|
id: "tool_stale".to_string(),
|
||||||
tool_name: "exec_command".to_string(),
|
tool_name: "exec_command".to_string(),
|
||||||
description: "stale approval".to_string(),
|
description: "stale approval".to_string(),
|
||||||
|
|||||||
@@ -121,8 +121,11 @@ pub fn build_approval_key(tool_name: &str, input: &serde_json::Value) -> Approva
|
|||||||
let paths_hash = hash_patch_paths(input);
|
let paths_hash = hash_patch_paths(input);
|
||||||
format!("patch:{paths_hash}")
|
format!("patch:{paths_hash}")
|
||||||
}
|
}
|
||||||
"exec_shell" | "exec_shell_wait" | "exec_shell_interact"
|
"exec_shell"
|
||||||
| "exec_wait" | "exec_interact" => {
|
| "exec_shell_wait"
|
||||||
|
| "exec_shell_interact"
|
||||||
|
| "exec_wait"
|
||||||
|
| "exec_interact" => {
|
||||||
let prefix = command_prefix(input);
|
let prefix = command_prefix(input);
|
||||||
format!("shell:{prefix}")
|
format!("shell:{prefix}")
|
||||||
}
|
}
|
||||||
@@ -137,10 +140,7 @@ pub fn build_approval_key(tool_name: &str, input: &serde_json::Value) -> Approva
|
|||||||
|
|
||||||
/// Extract the first three non‑flag tokens from the command string.
|
/// Extract the first three non‑flag tokens from the command string.
|
||||||
fn command_prefix(input: &serde_json::Value) -> String {
|
fn command_prefix(input: &serde_json::Value) -> String {
|
||||||
let cmd = input
|
let cmd = input.get("command").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
.get("command")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
let tokens: Vec<&str> = cmd
|
let tokens: Vec<&str> = cmd
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.filter(|t| !t.starts_with('-'))
|
.filter(|t| !t.starts_with('-'))
|
||||||
@@ -190,10 +190,7 @@ fn hash_patch_paths(input: &serde_json::Value) -> String {
|
|||||||
|
|
||||||
/// Parse the host portion from a URL input.
|
/// Parse the host portion from a URL input.
|
||||||
fn parse_host(input: &serde_json::Value) -> String {
|
fn parse_host(input: &serde_json::Value) -> String {
|
||||||
let url = input
|
let url = input.get("url").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
.get("url")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
if let Ok(parsed) = reqwest::Url::parse(url) {
|
if let Ok(parsed) = reqwest::Url::parse(url) {
|
||||||
parsed.host_str().unwrap_or(url).to_string()
|
parsed.host_str().unwrap_or(url).to_string()
|
||||||
|
|||||||
@@ -607,10 +607,7 @@ impl ToolSpec for McpToolAdapter {
|
|||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
// McpTool.description is Option<String>; fall back to the
|
// McpTool.description is Option<String>; fall back to the
|
||||||
// prefixed name when absent.
|
// prefixed name when absent.
|
||||||
self.tool
|
self.tool.description.as_deref().unwrap_or(&self.name)
|
||||||
.description
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or(&self.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_schema(&self) -> Value {
|
fn input_schema(&self) -> Value {
|
||||||
@@ -645,18 +642,13 @@ impl ToolSpec for McpToolAdapter {
|
|||||||
!keep_loaded
|
!keep_loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(
|
async fn execute(&self, input: Value, _context: &ToolContext) -> Result<ToolResult, ToolError> {
|
||||||
&self,
|
|
||||||
input: Value,
|
|
||||||
_context: &ToolContext,
|
|
||||||
) -> Result<ToolResult, ToolError> {
|
|
||||||
let mut pool = self.pool.lock().await;
|
let mut pool = self.pool.lock().await;
|
||||||
let result = pool
|
let result = pool
|
||||||
.call_tool(&self.name, input)
|
.call_tool(&self.name, input)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ToolError::execution_failed(format!("MCP tool failed: {e}")))?;
|
.map_err(|e| ToolError::execution_failed(format!("MCP tool failed: {e}")))?;
|
||||||
let content =
|
let content = serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string());
|
||||||
serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string());
|
|
||||||
Ok(ToolResult::success(content))
|
Ok(ToolResult::success(content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -668,8 +668,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_request_new() {
|
fn test_approval_request_new() {
|
||||||
let params = json!({"path": "src/main.rs", "content": "test"});
|
let params = json!({"path": "src/main.rs", "content": "test"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "write_file", "Write a file to disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"write_file",
|
||||||
|
"Write a file to disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(request.id, "test-id");
|
assert_eq!(request.id, "test-id");
|
||||||
assert_eq!(request.tool_name, "write_file");
|
assert_eq!(request.tool_name, "write_file");
|
||||||
@@ -682,8 +687,13 @@ mod tests {
|
|||||||
// Create params with a very long string
|
// Create params with a very long string
|
||||||
let long_content = "x".repeat(300);
|
let long_content = "x".repeat(300);
|
||||||
let params = json!({"path": "src/main.rs", "content": long_content});
|
let params = json!({"path": "src/main.rs", "content": long_content});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "write_file", "Write a file to disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"write_file",
|
||||||
|
"Write a file to disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
|
|
||||||
let display = request.params_display();
|
let display = request.params_display();
|
||||||
// Should be truncated to around 200 chars
|
// Should be truncated to around 200 chars
|
||||||
@@ -694,8 +704,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_request_params_display_short() {
|
fn test_approval_request_params_display_short() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
|
|
||||||
let display = request.params_display();
|
let display = request.params_display();
|
||||||
assert!(display.contains("src/main.rs"));
|
assert!(display.contains("src/main.rs"));
|
||||||
@@ -704,7 +719,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_request_derives_impact_summary() {
|
fn test_approval_request_derives_impact_summary() {
|
||||||
let params = json!({"cmd": "cargo test", "workdir": "/tmp/project"});
|
let params = json!({"cmd": "cargo test", "workdir": "/tmp/project"});
|
||||||
let request = ApprovalRequest::new("test-id", "exec_shell", "Run a shell command", ¶ms, "test_key");
|
let request = ApprovalRequest::new(
|
||||||
|
"test-id",
|
||||||
|
"exec_shell",
|
||||||
|
"Run a shell command",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(request.category, ToolCategory::Shell);
|
assert_eq!(request.category, ToolCategory::Shell);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -728,8 +749,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_initial_state() {
|
fn test_approval_view_initial_state() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let view = ApprovalView::new(request.clone());
|
let view = ApprovalView::new(request.clone());
|
||||||
|
|
||||||
assert_eq!(view.selected, 0);
|
assert_eq!(view.selected, 0);
|
||||||
@@ -739,8 +765,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_navigation() {
|
fn test_approval_view_navigation() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request);
|
let mut view = ApprovalView::new(request);
|
||||||
|
|
||||||
// Initially at 0
|
// Initially at 0
|
||||||
@@ -768,8 +799,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_keybindings_decisions() {
|
fn test_approval_view_keybindings_decisions() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request.clone());
|
let mut view = ApprovalView::new(request.clone());
|
||||||
|
|
||||||
// Test 'y' -> Approved
|
// Test 'y' -> Approved
|
||||||
@@ -819,8 +855,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_enter_uses_selected_option() {
|
fn test_approval_view_enter_uses_selected_option() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request);
|
let mut view = ApprovalView::new(request);
|
||||||
|
|
||||||
// Navigate to index 2 (Denied)
|
// Navigate to index 2 (Denied)
|
||||||
@@ -842,8 +883,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_navigation_keys() {
|
fn test_approval_view_navigation_keys() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request);
|
let mut view = ApprovalView::new(request);
|
||||||
|
|
||||||
// Test Up arrow
|
// Test Up arrow
|
||||||
@@ -866,8 +912,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_view_params() {
|
fn test_approval_view_view_params() {
|
||||||
let params = json!({"path": "src/main.rs", "content": "test"});
|
let params = json!({"path": "src/main.rs", "content": "test"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request.clone());
|
let mut view = ApprovalView::new(request.clone());
|
||||||
|
|
||||||
// Test 'v' to view params
|
// Test 'v' to view params
|
||||||
@@ -889,8 +940,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_approval_view_current_decision_mapping() {
|
fn test_approval_view_current_decision_mapping() {
|
||||||
let params = json!({"path": "src/main.rs"});
|
let params = json!({"path": "src/main.rs"});
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new("test-id", "read_file", "Read a file from disk", ¶ms, "test_key");
|
"test-id",
|
||||||
|
"read_file",
|
||||||
|
"Read a file from disk",
|
||||||
|
¶ms,
|
||||||
|
"test_key",
|
||||||
|
);
|
||||||
let mut view = ApprovalView::new(request);
|
let mut view = ApprovalView::new(request);
|
||||||
|
|
||||||
// Index 0 -> Approved
|
// Index 0 -> Approved
|
||||||
|
|||||||
@@ -771,8 +771,9 @@ async fn run_event_loop(
|
|||||||
description,
|
description,
|
||||||
approval_key,
|
approval_key,
|
||||||
} => {
|
} => {
|
||||||
let session_approved = app.approval_session_approved.contains(&approval_key)
|
let session_approved =
|
||||||
|| app.approval_session_approved.contains(&tool_name);
|
app.approval_session_approved.contains(&approval_key)
|
||||||
|
|| app.approval_session_approved.contains(&tool_name);
|
||||||
if session_approved || app.approval_mode == ApprovalMode::Auto {
|
if session_approved || app.approval_mode == ApprovalMode::Auto {
|
||||||
log_sensitive_event(
|
log_sensitive_event(
|
||||||
"tool.approval.auto_approve",
|
"tool.approval.auto_approve",
|
||||||
@@ -809,8 +810,13 @@ async fn run_event_loop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create approval request and show overlay
|
// Create approval request and show overlay
|
||||||
let request =
|
let request = ApprovalRequest::new(
|
||||||
ApprovalRequest::new(&id, &tool_name, &description, &tool_input, &approval_key);
|
&id,
|
||||||
|
&tool_name,
|
||||||
|
&description,
|
||||||
|
&tool_input,
|
||||||
|
&approval_key,
|
||||||
|
);
|
||||||
log_sensitive_event(
|
log_sensitive_event(
|
||||||
"tool.approval.prompted",
|
"tool.approval.prompted",
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user