chore(lint): clean up frontend and Rust lint issues

This commit is contained in:
xintaofei
2026-04-23 15:56:41 +08:00
parent 1dd40d0baf
commit 022172a9ea
69 changed files with 1138 additions and 1248 deletions

View File

@@ -156,9 +156,7 @@ async fn build_agent(
debug_assert_eq!(meta.agent_type, agent_type);
match meta.distribution {
AgentDistribution::Npx {
cmd, args, env, ..
} => {
AgentDistribution::Npx { cmd, args, env, .. } => {
let merged_env = merge_agent_env(env, runtime_env);
let mut parts: Vec<String> = Vec::new();
for (k, v) in &merged_env {
@@ -251,10 +249,7 @@ async fn build_agent(
))
})?;
if cached_version == registry_version {
eprintln!(
"[ACP][{}] Using cached binary {cached_version}",
meta.name
);
eprintln!("[ACP][{}] Using cached binary {cached_version}", meta.name);
} else {
eprintln!(
"[ACP][{}] Using cached binary {cached_version} (registry recommends {registry_version})",
@@ -273,8 +268,7 @@ async fn build_agent(
server = server.args(cmd_args);
}
let merged_env = merge_agent_env(env, runtime_env);
let env_key_list: Vec<&str> =
merged_env.iter().map(|(k, _)| k.as_str()).collect();
let env_key_list: Vec<&str> = merged_env.iter().map(|(k, _)| k.as_str()).collect();
if !merged_env.is_empty() {
let env_vars: Vec<sacp::schema::EnvVariable> = merged_env
.iter()
@@ -314,34 +308,36 @@ async fn build_agent(
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
let agent_name = meta.name.to_string();
Ok(AcpAgent::new(sacp::schema::McpServer::Stdio(server)).with_debug(
move |line, dir| {
let (tag, enabled) = match dir {
sacp_tokio::LineDirection::Stderr => ("stderr", true),
sacp_tokio::LineDirection::Stdout => ("stdout", stdio_debug_enabled),
sacp_tokio::LineDirection::Stdin => ("stdin", stdio_debug_enabled),
};
if !enabled {
return;
}
const MAX: usize = 256;
if line.len() > MAX {
let head = line
.char_indices()
.take_while(|(i, _)| *i < MAX)
.last()
.map(|(i, c)| i + c.len_utf8())
.unwrap_or(MAX);
eprintln!(
"[ACP][{agent_name}][{tag}] {}... <truncated {} bytes>",
&line[..head],
line.len() - head
);
} else {
eprintln!("[ACP][{agent_name}][{tag}] {line}");
}
},
))
Ok(
AcpAgent::new(sacp::schema::McpServer::Stdio(server)).with_debug(
move |line, dir| {
let (tag, enabled) = match dir {
sacp_tokio::LineDirection::Stderr => ("stderr", true),
sacp_tokio::LineDirection::Stdout => ("stdout", stdio_debug_enabled),
sacp_tokio::LineDirection::Stdin => ("stdin", stdio_debug_enabled),
};
if !enabled {
return;
}
const MAX: usize = 256;
if line.len() > MAX {
let head = line
.char_indices()
.take_while(|(i, _)| *i < MAX)
.last()
.map(|(i, c)| i + c.len_utf8())
.unwrap_or(MAX);
eprintln!(
"[ACP][{agent_name}][{tag}] {}... <truncated {} bytes>",
&line[..head],
line.len() - head
);
} else {
eprintln!("[ACP][{agent_name}][{tag}] {line}");
}
},
),
)
}
}
}
@@ -574,37 +570,40 @@ fn ensure_codex_mode_option(options: &mut Vec<SessionConfigOptionInfo>) {
if options.iter().any(|o| o.id == "mode") {
return;
}
options.insert(0, SessionConfigOptionInfo {
id: "mode".to_string(),
name: "Approval Preset".to_string(),
description: Some(
"Choose an approval and sandboxing preset for your session".to_string(),
),
category: Some("mode".to_string()),
kind: SessionConfigKindInfo::Select(SessionConfigSelectInfo {
current_value: "auto".to_string(),
options: vec![
SessionConfigSelectOptionInfo {
value: "read-only".to_string(),
name: "Read Only".to_string(),
description: Some("Codex can only read files".to_string()),
},
SessionConfigSelectOptionInfo {
value: "auto".to_string(),
name: "Default".to_string(),
description: Some(
"Codex can edit files, but asks before running commands".to_string(),
),
},
SessionConfigSelectOptionInfo {
value: "full-access".to_string(),
name: "Full Access".to_string(),
description: Some("Codex runs without asking for approval".to_string()),
},
],
groups: vec![],
}),
});
options.insert(
0,
SessionConfigOptionInfo {
id: "mode".to_string(),
name: "Approval Preset".to_string(),
description: Some(
"Choose an approval and sandboxing preset for your session".to_string(),
),
category: Some("mode".to_string()),
kind: SessionConfigKindInfo::Select(SessionConfigSelectInfo {
current_value: "auto".to_string(),
options: vec![
SessionConfigSelectOptionInfo {
value: "read-only".to_string(),
name: "Read Only".to_string(),
description: Some("Codex can only read files".to_string()),
},
SessionConfigSelectOptionInfo {
value: "auto".to_string(),
name: "Default".to_string(),
description: Some(
"Codex can edit files, but asks before running commands".to_string(),
),
},
SessionConfigSelectOptionInfo {
value: "full-access".to_string(),
name: "Full Access".to_string(),
description: Some("Codex runs without asking for approval".to_string()),
},
],
groups: vec![],
}),
},
);
}
fn emit_session_config_options_values(
@@ -758,7 +757,15 @@ async fn run_connection(
async move |req: RequestPermissionRequest,
responder: Responder<RequestPermissionResponse>,
_cx: ConnectionTo<Agent>| {
handle_permission_request(&conn_id, &emitter_inner, &perms, &perm_cwd, req, responder).await;
handle_permission_request(
&conn_id,
&emitter_inner,
&perms,
&perm_cwd,
req,
responder,
)
.await;
Ok(())
}
},
@@ -979,7 +986,13 @@ async fn run_connection(
notif.update,
SessionUpdate::AvailableCommandsUpdate(_)
) {
emit_conversation_update(&cid, &h, agent_type, notif.update, None);
emit_conversation_update(
&cid,
&h,
agent_type,
notif.update,
None,
);
}
Ok(())
})
@@ -1004,7 +1017,12 @@ async fn run_connection(
},
);
emit_session_modes(&conn_id, &emitter_clone, session.modes());
emit_session_config_options(&conn_id, &emitter_clone, agent_type, &initial_config_options);
emit_session_config_options(
&conn_id,
&emitter_clone,
agent_type,
&initial_config_options,
);
emit_selectors_ready(&conn_id, &emitter_clone);
let loop_result = run_conversation_loop(
@@ -1058,9 +1076,7 @@ async fn run_connection(
"acp://event",
AcpEvent::Error {
connection_id: conn_id.clone(),
message: format!(
"Failed to load session, starting new: {e}"
),
message: format!("Failed to load session, starting new: {e}"),
agent_type: agent_type.to_string(),
code: None,
},
@@ -1072,8 +1088,7 @@ async fn run_connection(
.await?;
let fallback_sid = new_resp.session_id.0.to_string();
let initial_config_options = new_resp.config_options.clone();
let mut session =
cx.attach_session(new_resp, Default::default())?;
let mut session = cx.attach_session(new_resp, Default::default())?;
crate::web::event_bridge::emit_event(
&emitter_clone,
"acp://event",
@@ -1139,7 +1154,12 @@ async fn run_connection(
},
);
emit_session_modes(&conn_id, &emitter_clone, session.modes());
emit_session_config_options(&conn_id, &emitter_clone, agent_type, &initial_config_options);
emit_session_config_options(
&conn_id,
&emitter_clone,
agent_type,
&initial_config_options,
);
emit_selectors_ready(&conn_id, &emitter_clone);
let loop_result = run_conversation_loop(
@@ -1229,8 +1249,7 @@ async fn handle_permission_request(
obj.insert(key.to_string(), parsed);
}
} else if text.contains("@@\n") || text.contains("@@\r\n") {
if let Some(resolved) =
crate::parsers::resolve_patch_text(&text, Some(cwd))
if let Some(resolved) = crate::parsers::resolve_patch_text(&text, Some(cwd))
{
obj.insert(key.to_string(), serde_json::Value::String(resolved));
}
@@ -1759,7 +1778,14 @@ async fn handle_fork_or_exit(
// Recursively handle nested forks
Box::pin(handle_fork_or_exit(
loop_result, conn_id, emitter, agent_type, perms, cmd_rx, terminal_runtime, _cwd,
loop_result,
conn_id,
emitter,
agent_type,
perms,
cmd_rx,
terminal_runtime,
_cwd,
cwd_string,
))
.await
@@ -2169,8 +2195,10 @@ async fn run_conversation_loop<'a>(
}) => {
let cx = session.connection();
let sid = session.session_id().clone();
if let Err(e) =
set_session_config_option(&cx, &sid, conn_id, emitter, agent_type, config_id, value_id).await
if let Err(e) = set_session_config_option(
&cx, &sid, conn_id, emitter, agent_type, config_id, value_id,
)
.await
{
crate::web::event_bridge::emit_event(
emitter,
@@ -2499,10 +2527,10 @@ fn emit_conversation_update(
}
SessionUpdate::ToolCall(tc) => {
let content = serialize_tool_call_content(&tc.content);
let raw_input = json_value_to_text(&tc.raw_input)
.map(|text| resolve_live_tool_input(&text, cwd));
let raw_output = json_value_to_text(&tc.raw_output)
.map(|text| structurize_live_output(&text));
let raw_input =
json_value_to_text(&tc.raw_input).map(|text| resolve_live_tool_input(&text, cwd));
let raw_output =
json_value_to_text(&tc.raw_output).map(|text| structurize_live_output(&text));
let locations = if tc.locations.is_empty() {
None
} else {
@@ -2581,7 +2609,12 @@ fn emit_conversation_update(
);
}
SessionUpdate::ConfigOptionUpdate(update) => {
emit_session_config_options_values(connection_id, emitter, agent_type, update.config_options);
emit_session_config_options_values(
connection_id,
emitter,
agent_type,
update.config_options,
);
}
SessionUpdate::AvailableCommandsUpdate(update) => {
let commands: Vec<AvailableCommandInfo> = update
@@ -2661,8 +2694,8 @@ mod tests {
)
.unwrap();
let event = map_claude_sdk_ext_notification("conn-1", &raw)
.expect("valid sdk payload should map");
let event =
map_claude_sdk_ext_notification("conn-1", &raw).expect("valid sdk payload should map");
match event {
AcpEvent::ClaudeSdkMessage {
@@ -2700,11 +2733,8 @@ mod tests {
.unwrap();
assert!(map_claude_sdk_ext_notification("conn-1", &wrong_method).is_none());
let missing_fields = UntypedMessage::new(
"_claude/sdkMessage",
serde_json::json!({"sessionId": 1}),
)
.unwrap();
let missing_fields =
UntypedMessage::new("_claude/sdkMessage", serde_json::json!({"sessionId": 1})).unwrap();
assert!(map_claude_sdk_ext_notification("conn-1", &missing_fields).is_none());
}

View File

@@ -219,10 +219,7 @@ impl ConnectionManager {
pub async fn disconnect_all(&self) -> usize {
let cmd_txs: Vec<_> = {
let mut connections = self.connections.lock().await;
connections
.drain()
.map(|(_, conn)| conn.cmd_tx)
.collect()
connections.drain().map(|(_, conn)| conn.cmd_tx).collect()
};
let disconnected = cmd_txs.len();
for cmd_tx in cmd_txs {

View File

@@ -1,11 +1,11 @@
pub mod binary_cache;
pub mod connection;
pub mod error;
pub mod fork;
pub mod file_system_runtime;
pub mod fork;
pub mod manager;
pub mod opencode_plugins;
pub mod preflight;
pub mod registry;
pub mod terminal_runtime;
pub mod types;
pub mod opencode_plugins;

View File

@@ -88,9 +88,7 @@ fn has_project_opencode_config(project_root: &Path) -> bool {
/// Inspect `~/.config/opencode/opencode.json` and `~/.cache/opencode/node_modules/`
/// to determine which declared plugins are installed and which are missing.
pub fn check_opencode_plugins(
project_root: Option<&Path>,
) -> Result<PluginCheckSummary, String> {
pub fn check_opencode_plugins(project_root: Option<&Path>) -> Result<PluginCheckSummary, String> {
let config_path = opencode_config_path()
.ok_or_else(|| "Cannot determine opencode config directory".to_string())?;
let cache_dir = opencode_cache_dir()
@@ -257,12 +255,7 @@ fn write_backup_and_prune(path: &Path, content: &str, keep: usize) -> Result<(),
let mut backups: Vec<_> = fs::read_dir(parent)
.map_err(|e| e.to_string())?
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry
.file_name()
.to_string_lossy()
.starts_with(&prefix)
})
.filter(|entry| entry.file_name().to_string_lossy().starts_with(&prefix))
.collect();
// Sort by name descending (timestamp in name → newest first)
@@ -280,8 +273,8 @@ pub(crate) fn atomic_rewrite_opencode_json(
path: &Path,
mutator: impl FnOnce(&mut serde_json::Value) -> Result<(), String>,
) -> Result<(), String> {
let raw = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {e}", path.display()))?;
let raw =
fs::read_to_string(path).map_err(|e| format!("Failed to read {}: {e}", path.display()))?;
// Try parsing first. If serde_json succeeds the file is valid JSON
// and any "//" or "/*" sequences live inside string values — not real
@@ -302,14 +295,12 @@ pub(crate) fn atomic_rewrite_opencode_json(
mutator(&mut doc)?;
let new_raw = serde_json::to_string_pretty(&doc)
.map_err(|e| format!("Failed to serialize JSON: {e}"))?;
let new_raw =
serde_json::to_string_pretty(&doc).map_err(|e| format!("Failed to serialize JSON: {e}"))?;
let tmp_path = path.with_extension("json.tmp");
fs::write(&tmp_path, &new_raw)
.map_err(|e| format!("Failed to write temp file: {e}"))?;
fs::rename(&tmp_path, path)
.map_err(|e| format!("Failed to rename temp file: {e}"))?;
fs::write(&tmp_path, &new_raw).map_err(|e| format!("Failed to write temp file: {e}"))?;
fs::rename(&tmp_path, path).map_err(|e| format!("Failed to rename temp file: {e}"))?;
Ok(())
}
@@ -366,12 +357,9 @@ fn pin_latest_specs(
for item in arr.iter_mut() {
if let Some(spec_str) = item.as_str() {
if let Some((parsed_name, _)) = parse_plugin_spec(spec_str) {
if let Some((_, version)) =
pin_map.iter().find(|(n, _)| *n == parsed_name)
if let Some((_, version)) = pin_map.iter().find(|(n, _)| *n == parsed_name)
{
*item = serde_json::Value::String(format!(
"{parsed_name}@{version}"
));
*item = serde_json::Value::String(format!("{parsed_name}@{version}"));
pinned += 1;
}
}
@@ -417,9 +405,9 @@ pub async fn install_missing_plugins(
task_id: String,
emitter: &EventEmitter,
) -> Result<(), String> {
let _guard = PLUGIN_OP_LOCK.try_lock().map_err(|_| {
"Another plugin operation is in progress".to_string()
})?;
let _guard = PLUGIN_OP_LOCK
.try_lock()
.map_err(|_| "Another plugin operation is in progress".to_string())?;
emit_plugin_event(emitter, &task_id, PluginInstallEventKind::Started, "");
@@ -597,12 +585,14 @@ pub async fn install_missing_plugins(
/// Uninstall a single plugin: remove from opencode.json, then `bun remove` from cache.
pub async fn uninstall_plugin(name: String) -> Result<PluginCheckSummary, String> {
let _guard = PLUGIN_OP_LOCK.try_lock().map_err(|_| {
"Another plugin operation is in progress".to_string()
})?;
let _guard = PLUGIN_OP_LOCK
.try_lock()
.map_err(|_| "Another plugin operation is in progress".to_string())?;
if is_protected_package(&name) {
return Err(format!("Cannot uninstall {name}: it is an internal opencode package"));
return Err(format!(
"Cannot uninstall {name}: it is an internal opencode package"
));
}
let config_path = opencode_config_path()

View File

@@ -102,14 +102,30 @@ async fn check_npm_environment(node_required: Option<&str>) -> Vec<CheckItem> {
let (node_result, npm_result) = tokio::join!(
async {
match &node_path {
Some(p) => crate::process::tokio_command(p).arg("--version").output().await,
None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "node not found in PATH")),
Some(p) => {
crate::process::tokio_command(p)
.arg("--version")
.output()
.await
}
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"node not found in PATH",
)),
}
},
async {
match &npm_path {
Some(p) => crate::process::tokio_command(p).arg("--version").output().await,
None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "npm not found in PATH")),
Some(p) => {
crate::process::tokio_command(p)
.arg("--version")
.output()
.await
}
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"npm not found in PATH",
)),
}
},
);
@@ -310,47 +326,44 @@ async fn check_binary_environment(
// but still pass — the Settings page's version-badge flow is the
// canonical place to surface "upgrade available".
if platform_supported {
let cache_check =
match binary_cache::find_best_cached_binary_for_agent(agent_type, cmd) {
Ok(Some((_, cached_version))) => {
let message = if cached_version == version {
"Binary is cached locally".to_string()
} else {
format!(
"Binary {cached_version} is cached locally (recommended: {version})"
)
};
CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Pass,
message,
fixes: vec![],
}
let cache_check = match binary_cache::find_best_cached_binary_for_agent(agent_type, cmd) {
Ok(Some((_, cached_version))) => {
let message = if cached_version == version {
"Binary is cached locally".to_string()
} else {
format!("Binary {cached_version} is cached locally (recommended: {version})")
};
CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Pass,
message,
fixes: vec![],
}
Ok(None) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message:
"Binary is not installed. Download it from Agent Settings before connecting."
.into(),
fixes: vec![],
},
Err(_) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message: "Cannot determine binary cache path".into(),
fixes: vec![],
},
};
}
Ok(None) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message:
"Binary is not installed. Download it from Agent Settings before connecting."
.into(),
fixes: vec![],
},
Err(_) => CheckItem {
check_id: "binary_cached".into(),
label: "Binary cache".into(),
status: CheckStatus::Warn,
message: "Cannot determine binary cache path".into(),
fixes: vec![],
},
};
checks.push(cache_check);
}
// OpenCode plugin checks
if agent_type == AgentType::OpenCode {
use crate::acp::opencode_plugins::{self, PluginStatus, spec_has_floating_version};
use crate::acp::opencode_plugins::{self, spec_has_floating_version, PluginStatus};
match opencode_plugins::check_opencode_plugins(None) {
Ok(summary) => {
let missing: Vec<_> = summary
@@ -376,8 +389,7 @@ async fn check_binary_environment(
fixes: vec![],
});
} else {
let names: Vec<&str> =
missing.iter().map(|p| p.name.as_str()).collect();
let names: Vec<&str> = missing.iter().map(|p| p.name.as_str()).collect();
checks.push(CheckItem {
check_id: "opencode_plugins".into(),
label: "OpenCode plugins".into(),

View File

@@ -38,8 +38,9 @@ pub struct AcpAgentMeta {
impl AcpAgentMeta {
pub fn registry_version(&self) -> Option<&'static str> {
match &self.distribution {
AgentDistribution::Npx { version, .. }
| AgentDistribution::Binary { version, .. } => Some(*version),
AgentDistribution::Npx { version, .. } | AgentDistribution::Binary { version, .. } => {
Some(*version)
}
}
}
}

View File

@@ -3,10 +3,9 @@ use std::process::Stdio;
use std::sync::Arc;
use sacp::schema::{
CreateTerminalRequest, CreateTerminalResponse, KillTerminalRequest,
KillTerminalResponse, ReleaseTerminalRequest, ReleaseTerminalResponse,
TerminalExitStatus, TerminalOutputRequest, TerminalOutputResponse, WaitForTerminalExitRequest,
WaitForTerminalExitResponse,
CreateTerminalRequest, CreateTerminalResponse, KillTerminalRequest, KillTerminalResponse,
ReleaseTerminalRequest, ReleaseTerminalResponse, TerminalExitStatus, TerminalOutputRequest,
TerminalOutputResponse, WaitForTerminalExitRequest, WaitForTerminalExitResponse,
};
use tokio::io::{AsyncRead, AsyncReadExt};
use tokio::sync::Mutex;