mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Fix exec inheritance of root shared flags (#18630)
Addresses #18113 Problem: Shared flags provided before the exec subcommand were parsed by the root CLI but not inherited by the exec CLI, so exec sessions could run with stale or default sandbox and model configuration. Solution: Move shared TUI and exec flags into a common option block and merge root selections into exec before dispatch, while preserving exec's global subcommand flag behavior.
This commit is contained in:
committed by
GitHub
Unverified
parent
2af4f15479
commit
0f1c9b8963
+43
-42
@@ -717,6 +717,9 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"exec",
|
||||
)?;
|
||||
exec_cli
|
||||
.shared
|
||||
.inherit_exec_root_options(&interactive.shared);
|
||||
prepend_config_flags(
|
||||
&mut exec_cli.config_overrides,
|
||||
root_config_overrides.clone(),
|
||||
@@ -1241,6 +1244,7 @@ async fn run_debug_prompt_input_command(
|
||||
interactive: TuiCli,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
) -> anyhow::Result<()> {
|
||||
let shared = interactive.shared.into_inner();
|
||||
let mut cli_kv_overrides = root_config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
@@ -1251,38 +1255,38 @@ async fn run_debug_prompt_input_command(
|
||||
));
|
||||
}
|
||||
|
||||
let approval_policy = if interactive.full_auto {
|
||||
let approval_policy = if shared.full_auto {
|
||||
Some(AskForApproval::OnRequest)
|
||||
} else if interactive.dangerously_bypass_approvals_and_sandbox {
|
||||
} else if shared.dangerously_bypass_approvals_and_sandbox {
|
||||
Some(AskForApproval::Never)
|
||||
} else {
|
||||
interactive.approval_policy.map(Into::into)
|
||||
};
|
||||
let sandbox_mode = if interactive.full_auto {
|
||||
let sandbox_mode = if shared.full_auto {
|
||||
Some(codex_protocol::config_types::SandboxMode::WorkspaceWrite)
|
||||
} else if interactive.dangerously_bypass_approvals_and_sandbox {
|
||||
} else if shared.dangerously_bypass_approvals_and_sandbox {
|
||||
Some(codex_protocol::config_types::SandboxMode::DangerFullAccess)
|
||||
} else {
|
||||
interactive.sandbox_mode.map(Into::into)
|
||||
shared.sandbox_mode.map(Into::into)
|
||||
};
|
||||
let overrides = ConfigOverrides {
|
||||
model: interactive.model,
|
||||
config_profile: interactive.config_profile,
|
||||
model: shared.model,
|
||||
config_profile: shared.config_profile,
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
cwd: interactive.cwd,
|
||||
cwd: shared.cwd,
|
||||
codex_self_exe: arg0_paths.codex_self_exe,
|
||||
codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe,
|
||||
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe,
|
||||
show_raw_agent_reasoning: interactive.oss.then_some(true),
|
||||
show_raw_agent_reasoning: shared.oss.then_some(true),
|
||||
ephemeral: Some(true),
|
||||
additional_writable_roots: interactive.add_dir,
|
||||
additional_writable_roots: shared.add_dir,
|
||||
..Default::default()
|
||||
};
|
||||
let config =
|
||||
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
|
||||
|
||||
let mut input = interactive
|
||||
let mut input = shared
|
||||
.images
|
||||
.into_iter()
|
||||
.chain(cmd.images)
|
||||
@@ -1553,40 +1557,24 @@ fn finalize_fork_interactive(
|
||||
/// root-level flags. Only overrides fields explicitly set on the subcommand-scoped
|
||||
/// CLI. Also appends `-c key=value` overrides with highest precedence.
|
||||
fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli) {
|
||||
if let Some(model) = subcommand_cli.model {
|
||||
interactive.model = Some(model);
|
||||
}
|
||||
if subcommand_cli.oss {
|
||||
interactive.oss = true;
|
||||
}
|
||||
if let Some(profile) = subcommand_cli.config_profile {
|
||||
interactive.config_profile = Some(profile);
|
||||
}
|
||||
if let Some(sandbox) = subcommand_cli.sandbox_mode {
|
||||
interactive.sandbox_mode = Some(sandbox);
|
||||
}
|
||||
if let Some(approval) = subcommand_cli.approval_policy {
|
||||
let TuiCli {
|
||||
shared,
|
||||
approval_policy,
|
||||
web_search,
|
||||
prompt,
|
||||
config_overrides,
|
||||
..
|
||||
} = subcommand_cli;
|
||||
interactive
|
||||
.shared
|
||||
.apply_subcommand_overrides(shared.into_inner());
|
||||
if let Some(approval) = approval_policy {
|
||||
interactive.approval_policy = Some(approval);
|
||||
}
|
||||
if subcommand_cli.full_auto {
|
||||
interactive.full_auto = true;
|
||||
}
|
||||
if subcommand_cli.dangerously_bypass_approvals_and_sandbox {
|
||||
interactive.dangerously_bypass_approvals_and_sandbox = true;
|
||||
}
|
||||
if let Some(cwd) = subcommand_cli.cwd {
|
||||
interactive.cwd = Some(cwd);
|
||||
}
|
||||
if subcommand_cli.web_search {
|
||||
if web_search {
|
||||
interactive.web_search = true;
|
||||
}
|
||||
if !subcommand_cli.images.is_empty() {
|
||||
interactive.images = subcommand_cli.images;
|
||||
}
|
||||
if !subcommand_cli.add_dir.is_empty() {
|
||||
interactive.add_dir.extend(subcommand_cli.add_dir);
|
||||
}
|
||||
if let Some(prompt) = subcommand_cli.prompt {
|
||||
if let Some(prompt) = prompt {
|
||||
// Normalize CRLF/CR to LF so CLI-provided text can't leak `\r` into TUI state.
|
||||
interactive.prompt = Some(prompt.replace("\r\n", "\n").replace('\r', "\n"));
|
||||
}
|
||||
@@ -1594,7 +1582,7 @@ fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli)
|
||||
interactive
|
||||
.config_overrides
|
||||
.raw_overrides
|
||||
.extend(subcommand_cli.config_overrides.raw_overrides);
|
||||
.extend(config_overrides.raw_overrides);
|
||||
}
|
||||
|
||||
fn print_completion(cmd: CompletionCommand) {
|
||||
@@ -1714,6 +1702,19 @@ mod tests {
|
||||
assert_eq!(args.prompt.as_deref(), Some("re-review"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangerous_bypass_conflicts_with_approval_policy() {
|
||||
let err = MultitoolCli::try_parse_from([
|
||||
"codex",
|
||||
"--dangerously-bypass-approvals-and-sandbox",
|
||||
"--ask-for-approval",
|
||||
"on-request",
|
||||
])
|
||||
.expect_err("conflicting permission flags should be rejected");
|
||||
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
fn app_server_from_args(args: &[&str]) -> AppServerCommand {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let Subcommand::AppServer(app_server) = cli.subcommand.expect("app-server present") else {
|
||||
|
||||
+68
-54
@@ -3,6 +3,7 @@ use clap::FromArgMatches;
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use codex_utils_cli::SharedCliOptions;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -15,65 +16,13 @@ pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(
|
||||
long = "image",
|
||||
short = 'i',
|
||||
value_name = "FILE",
|
||||
value_delimiter = ',',
|
||||
num_args = 1..
|
||||
)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm', global = true)]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Use open-source provider.
|
||||
#[arg(long = "oss", default_value_t = false)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider")]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's', value_enum)]
|
||||
pub sandbox_mode: Option<codex_utils_cli::SandboxModeCliArg>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p')]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution (--sandbox workspace-write).
|
||||
#[arg(long = "full-auto", default_value_t = false, global = true)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
global = true,
|
||||
conflicts_with = "full_auto"
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
||||
pub cwd: Option<PathBuf>,
|
||||
#[clap(flatten)]
|
||||
pub shared: ExecSharedCliOptions,
|
||||
|
||||
/// Allow running Codex outside a Git repository.
|
||||
#[arg(long = "skip-git-repo-check", global = true, default_value_t = false)]
|
||||
pub skip_git_repo_check: bool,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
/// Run without persisting session files to disk.
|
||||
#[arg(long = "ephemeral", global = true, default_value_t = false)]
|
||||
pub ephemeral: bool,
|
||||
@@ -122,6 +71,71 @@ pub struct Cli {
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Cli {
|
||||
type Target = SharedCliOptions;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.shared.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Cli {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.shared.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ExecSharedCliOptions(SharedCliOptions);
|
||||
|
||||
impl ExecSharedCliOptions {
|
||||
pub fn into_inner(self) -> SharedCliOptions {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ExecSharedCliOptions {
|
||||
type Target = SharedCliOptions;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for ExecSharedCliOptions {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Args for ExecSharedCliOptions {
|
||||
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||
mark_exec_global_args(SharedCliOptions::augment_args(cmd))
|
||||
}
|
||||
|
||||
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||
mark_exec_global_args(SharedCliOptions::augment_args_for_update(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArgMatches for ExecSharedCliOptions {
|
||||
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
|
||||
SharedCliOptions::from_arg_matches(matches).map(Self)
|
||||
}
|
||||
|
||||
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
|
||||
self.0.update_from_arg_matches(matches)
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_exec_global_args(cmd: clap::Command) -> clap::Command {
|
||||
cmd.mut_arg("model", |arg| arg.global(true))
|
||||
.mut_arg("full_auto", |arg| arg.global(true))
|
||||
.mut_arg("dangerously_bypass_approvals_and_sandbox", |arg| {
|
||||
arg.global(true)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Command {
|
||||
/// Resume a previous session by id or pick the most recent with --last.
|
||||
|
||||
+15
-10
@@ -86,6 +86,7 @@ use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::canonicalize_existing_preserving_symlinks;
|
||||
use codex_utils_cli::SharedCliOptions;
|
||||
use codex_utils_oss::ensure_oss_provider_ready;
|
||||
use codex_utils_oss::get_default_model_for_oss_provider;
|
||||
use event_processor_with_human_output::EventProcessorWithHumanOutput;
|
||||
@@ -219,27 +220,31 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
|
||||
let Cli {
|
||||
command,
|
||||
images,
|
||||
model: model_cli_arg,
|
||||
oss,
|
||||
oss_provider,
|
||||
config_profile,
|
||||
full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox,
|
||||
cwd,
|
||||
shared,
|
||||
skip_git_repo_check,
|
||||
add_dir,
|
||||
ephemeral,
|
||||
ignore_user_config,
|
||||
ignore_rules,
|
||||
color,
|
||||
last_message_file,
|
||||
json: json_mode,
|
||||
sandbox_mode: sandbox_mode_cli_arg,
|
||||
prompt,
|
||||
output_schema: output_schema_path,
|
||||
config_overrides,
|
||||
} = cli;
|
||||
let shared = shared.into_inner();
|
||||
let SharedCliOptions {
|
||||
images,
|
||||
model: model_cli_arg,
|
||||
oss,
|
||||
oss_provider,
|
||||
config_profile,
|
||||
sandbox_mode: sandbox_mode_cli_arg,
|
||||
full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox,
|
||||
cwd,
|
||||
add_dir,
|
||||
} = shared;
|
||||
|
||||
let (_stdout_with_ansi, stderr_with_ansi) = match color {
|
||||
cli::Color::Always => (true, true),
|
||||
|
||||
+68
-51
@@ -1,8 +1,9 @@
|
||||
use clap::Args;
|
||||
use clap::FromArgMatches;
|
||||
use clap::Parser;
|
||||
use clap::ValueHint;
|
||||
use codex_utils_cli::ApprovalModeCliArg;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use std::path::PathBuf;
|
||||
use codex_utils_cli::SharedCliOptions;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version)]
|
||||
@@ -11,10 +12,6 @@ pub struct Cli {
|
||||
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(long = "image", short = 'i', value_name = "FILE", value_delimiter = ',', num_args = 1..)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
// Internal controls set by the top-level `codex resume` subcommand.
|
||||
// These are not exposed as user flags on the base `codex` command.
|
||||
#[clap(skip)]
|
||||
@@ -53,60 +50,17 @@ pub struct Cli {
|
||||
#[clap(skip)]
|
||||
pub fork_show_all: bool,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm')]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Convenience flag to select the local open source model provider. Equivalent to -c
|
||||
/// model_provider=oss; verifies a local LM Studio or Ollama server is running.
|
||||
#[arg(long = "oss", default_value_t = false)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider")]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p')]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's')]
|
||||
pub sandbox_mode: Option<codex_utils_cli::SandboxModeCliArg>,
|
||||
#[clap(flatten)]
|
||||
pub shared: TuiSharedCliOptions,
|
||||
|
||||
/// Configure when the model requires human approval before executing a command.
|
||||
#[arg(long = "ask-for-approval", short = 'a')]
|
||||
pub approval_policy: Option<ApprovalModeCliArg>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution (-a on-request, --sandbox workspace-write).
|
||||
#[arg(long = "full-auto", default_value_t = false)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
conflicts_with_all = ["approval_policy", "full_auto"]
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
/// In remote mode, the path is forwarded to the server and resolved there.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Enable live web search. When enabled, the native Responses `web_search` tool is available to the model (no per‑call approval).
|
||||
#[arg(long = "search", default_value_t = false)]
|
||||
pub web_search: bool,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
/// Disable alternate screen mode
|
||||
///
|
||||
/// Runs the TUI in inline mode, preserving terminal scrollback history. This is useful
|
||||
@@ -118,3 +72,66 @@ pub struct Cli {
|
||||
#[clap(skip)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Cli {
|
||||
type Target = SharedCliOptions;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.shared.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Cli {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.shared.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TuiSharedCliOptions(SharedCliOptions);
|
||||
|
||||
impl TuiSharedCliOptions {
|
||||
pub fn into_inner(self) -> SharedCliOptions {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for TuiSharedCliOptions {
|
||||
type Target = SharedCliOptions;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for TuiSharedCliOptions {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Args for TuiSharedCliOptions {
|
||||
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||
mark_tui_args(SharedCliOptions::augment_args(cmd))
|
||||
}
|
||||
|
||||
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||
mark_tui_args(SharedCliOptions::augment_args_for_update(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArgMatches for TuiSharedCliOptions {
|
||||
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
|
||||
SharedCliOptions::from_arg_matches(matches).map(Self)
|
||||
}
|
||||
|
||||
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
|
||||
self.0.update_from_arg_matches(matches)
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_tui_args(cmd: clap::Command) -> clap::Command {
|
||||
cmd.mut_arg("dangerously_bypass_approvals_and_sandbox", |arg| {
|
||||
arg.conflicts_with("approval_policy")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1387,10 +1387,11 @@ async fn run_ratatui_app(
|
||||
|
||||
let Cli {
|
||||
prompt,
|
||||
images,
|
||||
shared,
|
||||
no_alt_screen,
|
||||
..
|
||||
} = cli;
|
||||
let images = shared.into_inner().images;
|
||||
|
||||
let use_alt_screen = determine_alt_screen_mode(no_alt_screen, config.tui_alternate_screen);
|
||||
tui.set_alt_screen_enabled(use_alt_screen);
|
||||
|
||||
@@ -2,8 +2,10 @@ mod approval_mode_cli_arg;
|
||||
mod config_override;
|
||||
pub(crate) mod format_env_display;
|
||||
mod sandbox_mode_cli_arg;
|
||||
mod shared_options;
|
||||
|
||||
pub use approval_mode_cli_arg::ApprovalModeCliArg;
|
||||
pub use config_override::CliConfigOverrides;
|
||||
pub use format_env_display::format_env_display;
|
||||
pub use sandbox_mode_cli_arg::SandboxModeCliArg;
|
||||
pub use shared_options::SharedCliOptions;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
//! Shared command-line flags used by both interactive and non-interactive Codex entry points.
|
||||
|
||||
use crate::SandboxModeCliArg;
|
||||
use clap::Args;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
pub struct SharedCliOptions {
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(
|
||||
long = "image",
|
||||
short = 'i',
|
||||
value_name = "FILE",
|
||||
value_delimiter = ',',
|
||||
num_args = 1..
|
||||
)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm')]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Use open-source provider.
|
||||
#[arg(long = "oss", default_value_t = false)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider")]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p')]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's')]
|
||||
pub sandbox_mode: Option<SandboxModeCliArg>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution.
|
||||
#[arg(long = "full-auto", default_value_t = false)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
conflicts_with = "full_auto"
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl SharedCliOptions {
|
||||
pub fn inherit_exec_root_options(&mut self, root: &Self) {
|
||||
let self_selected_sandbox_mode = self.sandbox_mode.is_some()
|
||||
|| self.full_auto
|
||||
|| self.dangerously_bypass_approvals_and_sandbox;
|
||||
let Self {
|
||||
images,
|
||||
model,
|
||||
oss,
|
||||
oss_provider,
|
||||
config_profile,
|
||||
sandbox_mode,
|
||||
full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox,
|
||||
cwd,
|
||||
add_dir,
|
||||
} = self;
|
||||
let Self {
|
||||
images: root_images,
|
||||
model: root_model,
|
||||
oss: root_oss,
|
||||
oss_provider: root_oss_provider,
|
||||
config_profile: root_config_profile,
|
||||
sandbox_mode: root_sandbox_mode,
|
||||
full_auto: root_full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox: root_dangerously_bypass_approvals_and_sandbox,
|
||||
cwd: root_cwd,
|
||||
add_dir: root_add_dir,
|
||||
} = root;
|
||||
|
||||
if model.is_none() {
|
||||
model.clone_from(root_model);
|
||||
}
|
||||
if *root_oss {
|
||||
*oss = true;
|
||||
}
|
||||
if oss_provider.is_none() {
|
||||
oss_provider.clone_from(root_oss_provider);
|
||||
}
|
||||
if config_profile.is_none() {
|
||||
config_profile.clone_from(root_config_profile);
|
||||
}
|
||||
if sandbox_mode.is_none() {
|
||||
*sandbox_mode = *root_sandbox_mode;
|
||||
}
|
||||
if !self_selected_sandbox_mode {
|
||||
*full_auto = *root_full_auto;
|
||||
*dangerously_bypass_approvals_and_sandbox =
|
||||
*root_dangerously_bypass_approvals_and_sandbox;
|
||||
}
|
||||
if cwd.is_none() {
|
||||
cwd.clone_from(root_cwd);
|
||||
}
|
||||
if !root_images.is_empty() {
|
||||
let mut merged_images = root_images.clone();
|
||||
merged_images.append(images);
|
||||
*images = merged_images;
|
||||
}
|
||||
if !root_add_dir.is_empty() {
|
||||
let mut merged_add_dir = root_add_dir.clone();
|
||||
merged_add_dir.append(add_dir);
|
||||
*add_dir = merged_add_dir;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_subcommand_overrides(&mut self, subcommand: Self) {
|
||||
let subcommand_selected_sandbox_mode = subcommand.sandbox_mode.is_some()
|
||||
|| subcommand.full_auto
|
||||
|| subcommand.dangerously_bypass_approvals_and_sandbox;
|
||||
let Self {
|
||||
images,
|
||||
model,
|
||||
oss,
|
||||
oss_provider,
|
||||
config_profile,
|
||||
sandbox_mode,
|
||||
full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox,
|
||||
cwd,
|
||||
add_dir,
|
||||
} = subcommand;
|
||||
|
||||
if let Some(model) = model {
|
||||
self.model = Some(model);
|
||||
}
|
||||
if oss {
|
||||
self.oss = true;
|
||||
}
|
||||
if let Some(oss_provider) = oss_provider {
|
||||
self.oss_provider = Some(oss_provider);
|
||||
}
|
||||
if let Some(config_profile) = config_profile {
|
||||
self.config_profile = Some(config_profile);
|
||||
}
|
||||
if subcommand_selected_sandbox_mode {
|
||||
self.sandbox_mode = sandbox_mode;
|
||||
self.full_auto = full_auto;
|
||||
self.dangerously_bypass_approvals_and_sandbox =
|
||||
dangerously_bypass_approvals_and_sandbox;
|
||||
}
|
||||
if let Some(cwd) = cwd {
|
||||
self.cwd = Some(cwd);
|
||||
}
|
||||
if !images.is_empty() {
|
||||
self.images = images;
|
||||
}
|
||||
if !add_dir.is_empty() {
|
||||
self.add_dir.extend(add_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user