mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
9352c6b235
Constrain `approval_policy` through new `admin_policy` config. This PR will: 1. Add a `admin_policy` section to config, with a single field (for now) `allowed_approval_policies`. This list constrains the set of user-settable `approval_policy`s. 2. Introduce a new `Constrained<T>` type, which combines a current value and a validator function. The validator function ensures disallowed values are not set. 3. Change the type of `approval_policy` on `Config` and `SessionConfiguration` from `AskForApproval` to `Constrained<AskForApproval>`. The validator function is set by the values passed into `allowed_approval_policies`. 4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When set, it disables selection of the value and indicates as such in the menu. This also makes it unselectable with arrow keys or numbers. This is used in the `/approvals` menu. Follow ups are: 1. Do the same thing to `sandbox_policy`. 2. Propagate the allowed set of values through app-server for the extension (though already this should prevent app-server from setting this values, it's just that we want to disable UI elements that are unsettable). Happy to split this PR up if you prefer, into the logical numbered areas above. Especially if there are parts we want to gavel on separately (e.g. admin_policy). Disabled full access: <img width="1680" height="380" alt="image" src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0" /> Disabled `--yolo` on startup: <img width="749" height="76" alt="image" src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb" /> CODEX-4087
79 lines
3.1 KiB
Rust
79 lines
3.1 KiB
Rust
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
|
|
|
use codex_core::AuthManager;
|
|
use codex_core::CodexAuth;
|
|
use codex_core::ConversationManager;
|
|
use codex_core::NewConversation;
|
|
use codex_core::built_in_model_providers;
|
|
use codex_core::protocol::EventMsg;
|
|
use codex_core::protocol::InitialHistory;
|
|
use codex_core::protocol::ResumedHistory;
|
|
use codex_core::protocol::RolloutItem;
|
|
use codex_core::protocol::TurnContextItem;
|
|
use codex_core::protocol::WarningEvent;
|
|
use codex_protocol::ConversationId;
|
|
use core::time::Duration;
|
|
use core_test_support::load_default_config_for_test;
|
|
use core_test_support::wait_for_event;
|
|
use tempfile::TempDir;
|
|
|
|
fn resume_history(
|
|
config: &codex_core::config::Config,
|
|
previous_model: &str,
|
|
rollout_path: &std::path::Path,
|
|
) -> InitialHistory {
|
|
let turn_ctx = TurnContextItem {
|
|
cwd: config.cwd.clone(),
|
|
approval_policy: config.approval_policy.value(),
|
|
sandbox_policy: config.sandbox_policy.clone(),
|
|
model: previous_model.to_string(),
|
|
effort: config.model_reasoning_effort,
|
|
summary: config.model_reasoning_summary,
|
|
};
|
|
|
|
InitialHistory::Resumed(ResumedHistory {
|
|
conversation_id: ConversationId::default(),
|
|
history: vec![RolloutItem::TurnContext(turn_ctx)],
|
|
rollout_path: rollout_path.to_path_buf(),
|
|
})
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn emits_warning_when_resumed_model_differs() {
|
|
// Arrange a config with a current model and a prior rollout recorded under a different model.
|
|
let home = TempDir::new().expect("tempdir");
|
|
let mut config = load_default_config_for_test(&home);
|
|
config.model = Some("current-model".to_string());
|
|
// Ensure cwd is absolute (the helper sets it to the temp dir already).
|
|
assert!(config.cwd.is_absolute());
|
|
|
|
let rollout_path = home.path().join("rollout.jsonl");
|
|
std::fs::write(&rollout_path, "").expect("create rollout placeholder");
|
|
|
|
let initial_history = resume_history(&config, "previous-model", &rollout_path);
|
|
|
|
let conversation_manager = ConversationManager::with_models_provider(
|
|
CodexAuth::from_api_key("test"),
|
|
config.model_provider.clone(),
|
|
);
|
|
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test"));
|
|
|
|
// Act: resume the conversation.
|
|
let NewConversation { conversation, .. } = conversation_manager
|
|
.resume_conversation_with_history(config, initial_history, auth_manager)
|
|
.await
|
|
.expect("resume conversation");
|
|
|
|
// Assert: a Warning event is emitted describing the model mismatch.
|
|
let warning = wait_for_event(&conversation, |ev| matches!(ev, EventMsg::Warning(_))).await;
|
|
let EventMsg::Warning(WarningEvent { message }) = warning else {
|
|
panic!("expected warning event");
|
|
};
|
|
assert!(message.contains("previous-model"));
|
|
assert!(message.contains("current-model"));
|
|
|
|
// Drain the TaskComplete/Shutdown window to avoid leaking tasks between tests.
|
|
// The warning is emitted during initialization, so a short sleep is sufficient.
|
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
}
|