解决codex在会话页面有时不返回权限配置选项

This commit is contained in:
xintaofei
2026-03-30 14:00:42 +08:00
parent 36ea3ef4bd
commit 9b9169f61d

View File

@@ -169,7 +169,16 @@ async fn build_agent(
} }
} }
let refs: Vec<&str> = parts.iter().map(|s| s.as_str()).collect(); let refs: Vec<&str> = parts.iter().map(|s| s.as_str()).collect();
AcpAgent::from_args(&refs).map_err(|e| AcpError::SpawnFailed(e.to_string())) let agent_name = meta.name.to_string();
AcpAgent::from_args(&refs)
.map(|a| {
a.with_debug(move |line, dir| {
if dir == sacp_tokio::LineDirection::Stderr {
eprintln!("[ACP][{agent_name}][stderr] {line}");
}
})
})
.map_err(|e| AcpError::SpawnFailed(e.to_string()))
} }
AgentDistribution::Binary { AgentDistribution::Binary {
version, version,
@@ -223,7 +232,14 @@ async fn build_agent(
.collect(); .collect();
server = server.env(env_vars); server = server.env(env_vars);
} }
Ok(AcpAgent::new(sacp::schema::McpServer::Stdio(server))) let agent_name = meta.name.to_string();
Ok(AcpAgent::new(sacp::schema::McpServer::Stdio(server)).with_debug(
move |line, dir| {
if dir == sacp_tokio::LineDirection::Stderr {
eprintln!("[ACP][{agent_name}][stderr] {line}");
}
},
))
} }
} }
} }
@@ -257,6 +273,7 @@ pub async fn spawn_agent_connection(
let result = run_connection( let result = run_connection(
agent, agent,
conn_id.clone(), conn_id.clone(),
agent_type,
working_dir, working_dir,
session_id, session_id,
cmd_rx, cmd_rx,
@@ -415,12 +432,59 @@ fn map_session_config_options(
.collect() .collect()
} }
/// Codex-acp sometimes omits the "mode" (approval preset) config option when
/// the loaded sandbox policy does not exactly match one of the three built-in
/// presets (commonly because `writable_roots` was injected during config
/// loading). When that happens, synthesize the option so the user can still
/// pick a preset. codex-acp's `set_config_option` handler always accepts
/// `config_id = "mode"` regardless of whether it was advertised.
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![],
}),
});
}
fn emit_session_config_options_values( fn emit_session_config_options_values(
connection_id: &str, connection_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
config_options: Vec<SessionConfigOption>, config_options: Vec<SessionConfigOption>,
) { ) {
let mapped = map_session_config_options(&config_options); let mut mapped = map_session_config_options(&config_options);
if agent_type == AgentType::Codex {
ensure_codex_mode_option(&mut mapped);
}
crate::web::event_bridge::emit_event( crate::web::event_bridge::emit_event(
emitter, emitter,
"acp://event", "acp://event",
@@ -434,6 +498,7 @@ fn emit_session_config_options_values(
fn emit_session_config_options( fn emit_session_config_options(
connection_id: &str, connection_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
config_options: &Option<Vec<SessionConfigOption>>, config_options: &Option<Vec<SessionConfigOption>>,
) { ) {
// Always emit one config-options snapshot after session attach. // Always emit one config-options snapshot after session attach.
@@ -441,7 +506,7 @@ fn emit_session_config_options(
// and return `None`; emitting an empty list lets the frontend settle // and return `None`; emitting an empty list lets the frontend settle
// loading state instead of waiting forever. // loading state instead of waiting forever.
let options = config_options.clone().unwrap_or_default(); let options = config_options.clone().unwrap_or_default();
emit_session_config_options_values(connection_id, emitter, options); emit_session_config_options_values(connection_id, emitter, agent_type, options);
} }
fn emit_selectors_ready(connection_id: &str, emitter: &EventEmitter) { fn emit_selectors_ready(connection_id: &str, emitter: &EventEmitter) {
@@ -492,6 +557,7 @@ fn resolve_working_dir(working_dir: Option<&str>) -> PathBuf {
async fn run_connection( async fn run_connection(
agent: AcpAgent, agent: AcpAgent,
connection_id: String, connection_id: String,
agent_type: AgentType,
working_dir: Option<String>, working_dir: Option<String>,
session_id: Option<String>, session_id: Option<String>,
mut cmd_rx: mpsc::Receiver<ConnectionCommand>, mut cmd_rx: mpsc::Receiver<ConnectionCommand>,
@@ -691,7 +757,7 @@ async fn run_connection(
notif.update, notif.update,
SessionUpdate::AvailableCommandsUpdate(_) SessionUpdate::AvailableCommandsUpdate(_)
) { ) {
emit_conversation_update(&cid, &h, notif.update, None); emit_conversation_update(&cid, &h, agent_type, notif.update, None);
} }
Ok(()) Ok(())
}) })
@@ -712,13 +778,14 @@ async fn run_connection(
}, },
); );
emit_session_modes(&conn_id, &emitter_clone, session.modes()); emit_session_modes(&conn_id, &emitter_clone, session.modes());
emit_session_config_options(&conn_id, &emitter_clone, &initial_config_options); emit_session_config_options(&conn_id, &emitter_clone, agent_type, &initial_config_options);
emit_selectors_ready(&conn_id, &emitter_clone); emit_selectors_ready(&conn_id, &emitter_clone);
let loop_result = run_conversation_loop( let loop_result = run_conversation_loop(
&mut session, &mut session,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -732,6 +799,7 @@ async fn run_connection(
loop_result, loop_result,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -784,6 +852,7 @@ async fn run_connection(
emit_session_config_options( emit_session_config_options(
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&initial_config_options, &initial_config_options,
); );
emit_selectors_ready(&conn_id, &emitter_clone); emit_selectors_ready(&conn_id, &emitter_clone);
@@ -792,6 +861,7 @@ async fn run_connection(
&mut session, &mut session,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -807,6 +877,7 @@ async fn run_connection(
loop_result, loop_result,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -834,13 +905,14 @@ async fn run_connection(
}, },
); );
emit_session_modes(&conn_id, &emitter_clone, session.modes()); emit_session_modes(&conn_id, &emitter_clone, session.modes());
emit_session_config_options(&conn_id, &emitter_clone, &initial_config_options); emit_session_config_options(&conn_id, &emitter_clone, agent_type, &initial_config_options);
emit_selectors_ready(&conn_id, &emitter_clone); emit_selectors_ready(&conn_id, &emitter_clone);
let loop_result = run_conversation_loop( let loop_result = run_conversation_loop(
&mut session, &mut session,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -854,6 +926,7 @@ async fn run_connection(
loop_result, loop_result,
&conn_id, &conn_id,
&emitter_clone, &emitter_clone,
agent_type,
&perms, &perms,
&mut cmd_rx, &mut cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -991,6 +1064,7 @@ async fn set_session_config_option(
session_id: &SessionId, session_id: &SessionId,
conn_id: &str, conn_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
config_id: String, config_id: String,
value_id: String, value_id: String,
) -> Result<(), sacp::Error> { ) -> Result<(), sacp::Error> {
@@ -1005,7 +1079,7 @@ async fn set_session_config_option(
sacp::util::internal_error(format!("Failed to parse config option response: {e}")) sacp::util::internal_error(format!("Failed to parse config option response: {e}"))
})?; })?;
emit_session_config_options_values(conn_id, emitter, response.config_options); emit_session_config_options_values(conn_id, emitter, agent_type, response.config_options);
Ok(()) Ok(())
} }
@@ -1376,6 +1450,7 @@ async fn handle_fork_or_exit(
loop_result: Result<Option<ForkExitInfo>, sacp::Error>, loop_result: Result<Option<ForkExitInfo>, sacp::Error>,
conn_id: &str, conn_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
perms: &PendingPermissions, perms: &PendingPermissions,
cmd_rx: &mut mpsc::Receiver<ConnectionCommand>, cmd_rx: &mut mpsc::Receiver<ConnectionCommand>,
terminal_runtime: Arc<TerminalRuntime>, terminal_runtime: Arc<TerminalRuntime>,
@@ -1421,13 +1496,14 @@ async fn handle_fork_or_exit(
}, },
); );
emit_session_modes(conn_id, emitter, session.modes()); emit_session_modes(conn_id, emitter, session.modes());
emit_session_config_options(conn_id, emitter, &initial_config_options); emit_session_config_options(conn_id, emitter, agent_type, &initial_config_options);
emit_selectors_ready(conn_id, emitter); emit_selectors_ready(conn_id, emitter);
let loop_result = run_conversation_loop( let loop_result = run_conversation_loop(
&mut session, &mut session,
conn_id, conn_id,
emitter, emitter,
agent_type,
perms, perms,
cmd_rx, cmd_rx,
terminal_runtime.clone(), terminal_runtime.clone(),
@@ -1440,7 +1516,8 @@ async fn handle_fork_or_exit(
// Recursively handle nested forks // Recursively handle nested forks
Box::pin(handle_fork_or_exit( Box::pin(handle_fork_or_exit(
loop_result, conn_id, emitter, perms, cmd_rx, terminal_runtime, _cwd, cwd_string, loop_result, conn_id, emitter, agent_type, perms, cmd_rx, terminal_runtime, _cwd,
cwd_string,
)) ))
.await .await
} }
@@ -1454,6 +1531,7 @@ async fn run_conversation_loop<'a>(
session: &mut sacp::ActiveSession<'a, Agent>, session: &mut sacp::ActiveSession<'a, Agent>,
conn_id: &str, conn_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
perms: &PendingPermissions, perms: &PendingPermissions,
cmd_rx: &mut mpsc::Receiver<ConnectionCommand>, cmd_rx: &mut mpsc::Receiver<ConnectionCommand>,
terminal_runtime: Arc<TerminalRuntime>, terminal_runtime: Arc<TerminalRuntime>,
@@ -1475,7 +1553,7 @@ async fn run_conversation_loop<'a>(
let _ = MatchDispatch::new(dispatch) let _ = MatchDispatch::new(dispatch)
.if_notification( .if_notification(
async |notif: SessionNotification| { async |notif: SessionNotification| {
emit_conversation_update(&cid, &h, notif.update, cwd_opt); emit_conversation_update(&cid, &h, agent_type, notif.update, cwd_opt);
Ok(()) Ok(())
}, },
) )
@@ -1563,7 +1641,7 @@ async fn run_conversation_loop<'a>(
&notif.update, &notif.update,
&mut tracked_terminal_tool_calls, &mut tracked_terminal_tool_calls,
); );
emit_conversation_update(&cid, &h, notif.update, cwd_opt); emit_conversation_update(&cid, &h, agent_type, notif.update, cwd_opt);
if should_poll_now { if should_poll_now {
poll_tracked_terminal_tool_calls( poll_tracked_terminal_tool_calls(
runtime.as_ref(), runtime.as_ref(),
@@ -1698,6 +1776,7 @@ async fn run_conversation_loop<'a>(
&sid, &sid,
conn_id, conn_id,
emitter, emitter,
agent_type,
config_id, config_id,
value_id, value_id,
) )
@@ -1827,7 +1906,7 @@ async fn run_conversation_loop<'a>(
let cx = session.connection(); let cx = session.connection();
let sid = session.session_id().clone(); let sid = session.session_id().clone();
if let Err(e) = if let Err(e) =
set_session_config_option(&cx, &sid, conn_id, emitter, config_id, value_id).await set_session_config_option(&cx, &sid, conn_id, emitter, agent_type, config_id, value_id).await
{ {
crate::web::event_bridge::emit_event( crate::web::event_bridge::emit_event(
emitter, emitter,
@@ -2038,6 +2117,7 @@ fn map_plan_entries(plan: &Plan) -> Vec<PlanEntryInfo> {
fn emit_conversation_update( fn emit_conversation_update(
connection_id: &str, connection_id: &str,
emitter: &EventEmitter, emitter: &EventEmitter,
agent_type: AgentType,
update: SessionUpdate, update: SessionUpdate,
cwd: Option<&str>, cwd: Option<&str>,
) { ) {
@@ -2145,7 +2225,7 @@ fn emit_conversation_update(
); );
} }
SessionUpdate::ConfigOptionUpdate(update) => { SessionUpdate::ConfigOptionUpdate(update) => {
emit_session_config_options_values(connection_id, emitter, update.config_options); emit_session_config_options_values(connection_id, emitter, agent_type, update.config_options);
} }
SessionUpdate::AvailableCommandsUpdate(update) => { SessionUpdate::AvailableCommandsUpdate(update) => {
let commands: Vec<AvailableCommandInfo> = update let commands: Vec<AvailableCommandInfo> = update