解决codex在会话页面有时不返回权限配置选项
This commit is contained in:
@@ -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>(
|
|||||||
¬if.update,
|
¬if.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
|
||||||
|
|||||||
Reference in New Issue
Block a user