Inline role lock note loading

This commit is contained in:
Ahmed Ibrahim
2026-03-10 18:31:08 -07:00
parent b95bb198b7
commit b1d777d8ea
2 changed files with 98 additions and 38 deletions

View File

@@ -190,49 +190,37 @@ Available roles:
fn format_role(name: &str, declaration: &AgentRoleConfig) -> String {
if let Some(description) = &declaration.description {
let locked_settings_note = locked_settings_note(declaration);
let locked_settings_note = declaration
.config_file
.as_ref()
.and_then(|config_file| {
built_in::config_file_contents(config_file)
.map(str::to_owned)
.or_else(|| std::fs::read_to_string(config_file).ok())
})
.and_then(|contents| toml::from_str::<TomlValue>(&contents).ok())
.map(|role_toml| {
let locks_model = role_toml.get("model").is_some();
let locks_reasoning_effort =
role_toml.get("model_reasoning_effort").is_some();
match (locks_model, locks_reasoning_effort) {
(true, true) => "\n- This role's model and reasoning effort are set by the role and cannot be changed.",
(true, false) => {
"\n- This role's model is set by the role and cannot be changed."
}
(false, true) => {
"\n- This role's reasoning effort is set by the role and cannot be changed."
}
(false, false) => "",
}
})
.unwrap_or_default();
format!("{name}: {{\n{description}{locked_settings_note}\n}}")
} else {
format!("{name}: no description")
}
}
fn locked_settings_note(declaration: &AgentRoleConfig) -> String {
let Some(config_file) = declaration.config_file.as_ref() else {
return String::new();
};
let Some(contents) = role_config_contents(config_file) else {
return String::new();
};
let Ok(role_toml) = toml::from_str::<TomlValue>(&contents) else {
return String::new();
};
let locks_model = role_toml.get("model").is_some();
let locks_reasoning_effort = role_toml.get("model_reasoning_effort").is_some();
match (locks_model, locks_reasoning_effort) {
(true, true) => {
"\n- This role's model and reasoning effort are set by the role and cannot be changed.".to_string()
}
(true, false) => {
"\n- This role's model is set by the role and cannot be changed.".to_string()
}
(false, true) => {
"\n- This role's reasoning effort is set by the role and cannot be changed."
.to_string()
}
(false, false) => String::new(),
}
}
fn role_config_contents(config_file: &Path) -> Option<String> {
built_in::config_file_contents(config_file)
.map(str::to_owned)
.or_else(|| std::fs::read_to_string(config_file).ok())
}
}
mod built_in {

View File

@@ -63,6 +63,23 @@ fn has_subagent_notification(req: &ResponsesRequest) -> bool {
.any(|text| text.contains("<subagent_notification>"))
}
fn tool_description(req: &ResponsesRequest, tool_name: &str) -> Option<String> {
req.body_json()
.get("tools")
.and_then(serde_json::Value::as_array)
.and_then(|tools| {
tools.iter().find_map(|tool| {
if tool.get("name").and_then(serde_json::Value::as_str) == Some(tool_name) {
tool.get("description")
.and_then(serde_json::Value::as_str)
.map(str::to_string)
} else {
None
}
})
})
}
async fn wait_for_spawned_thread_id(test: &TestCodex) -> Result<String> {
let deadline = Instant::now() + Duration::from_secs(2);
loop {
@@ -435,3 +452,58 @@ async fn spawn_agent_role_overrides_requested_model_and_reasoning_settings() ->
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn spawn_agent_tool_description_mentions_role_locked_settings() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let resp_mock = mount_sse_once_match(
&server,
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
sse(vec![
ev_response_created("resp-turn1-1"),
ev_assistant_message("msg-turn1-1", "done"),
ev_completed("resp-turn1-1"),
]),
)
.await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::Collab)
.expect("test config should allow feature update");
let role_path = config.codex_home.join("custom-role.toml");
std::fs::write(
&role_path,
format!(
"developer_instructions = \"Stay focused\"\nmodel = \"{ROLE_MODEL}\"\nmodel_reasoning_effort = \"{ROLE_REASONING_EFFORT}\"\n",
),
)
.expect("write role config");
config.agent_roles.insert(
"custom".to_string(),
AgentRoleConfig {
description: Some("Custom role".to_string()),
config_file: Some(role_path),
nickname_candidates: None,
},
);
});
let test = builder.build(&server).await?;
test.submit_turn(TURN_1_PROMPT).await?;
let request = resp_mock.single_request();
let spawn_description =
tool_description(&request, "spawn_agent").expect("spawn_agent description");
assert!(
spawn_description.contains(
"Custom role\n- This role's model and reasoning effort are set by the role and cannot be changed."
),
"expected locked-setting note in spawn_agent tool description: {spawn_description}"
);
Ok(())
}