feat: add nick name to sub-agents (#12320)

Adding random nick name to sub-agents. Used for UX

At the same time, also storing and wiring the role of the sub-agent
This commit is contained in:
jif-oai
2026-02-20 14:39:49 +00:00
committed by GitHub
parent 03ff04cd65
commit 0f9eed3a6f
39 changed files with 1125 additions and 109 deletions

View File

@@ -6658,6 +6658,8 @@ async fn read_summary_from_state_db_context_by_thread_id(
metadata.cwd,
metadata.cli_version,
metadata.source,
metadata.agent_nickname,
metadata.agent_role,
metadata.git_sha,
metadata.git_branch,
metadata.git_origin_url,
@@ -6678,9 +6680,12 @@ async fn summary_from_thread_list_item(
.unwrap_or_else(|| fallback_provider.to_string());
let cwd = it.cwd?;
let cli_version = it.cli_version.unwrap_or_default();
let source = it
.source
.unwrap_or(codex_protocol::protocol::SessionSource::Unknown);
let source = with_thread_spawn_agent_metadata(
it.source
.unwrap_or(codex_protocol::protocol::SessionSource::Unknown),
it.agent_nickname.clone(),
it.agent_role.clone(),
);
return Some(ConversationSummary {
conversation_id: thread_id,
path: it.path,
@@ -6735,13 +6740,17 @@ fn summary_from_state_db_metadata(
cwd: PathBuf,
cli_version: String,
source: String,
agent_nickname: Option<String>,
agent_role: Option<String>,
git_sha: Option<String>,
git_branch: Option<String>,
git_origin_url: Option<String>,
) -> ConversationSummary {
let preview = first_user_message.unwrap_or_default();
let source = serde_json::from_value(serde_json::Value::String(source))
let source = serde_json::from_str(&source)
.or_else(|_| serde_json::from_value(serde_json::Value::String(source.clone())))
.unwrap_or(codex_protocol::protocol::SessionSource::Unknown);
let source = with_thread_spawn_agent_metadata(source, agent_nickname, agent_role);
let git_info = if git_sha.is_none() && git_branch.is_none() && git_origin_url.is_none() {
None
} else {
@@ -6901,6 +6910,35 @@ fn map_git_info(git_info: &CoreGitInfo) -> ConversationGitInfo {
}
}
fn with_thread_spawn_agent_metadata(
source: codex_protocol::protocol::SessionSource,
agent_nickname: Option<String>,
agent_role: Option<String>,
) -> codex_protocol::protocol::SessionSource {
if agent_nickname.is_none() && agent_role.is_none() {
return source;
}
match source {
codex_protocol::protocol::SessionSource::SubAgent(
codex_protocol::protocol::SubAgentSource::ThreadSpawn {
parent_thread_id,
depth,
agent_nickname: existing_agent_nickname,
agent_role: existing_agent_role,
},
) => codex_protocol::protocol::SessionSource::SubAgent(
codex_protocol::protocol::SubAgentSource::ThreadSpawn {
parent_thread_id,
depth,
agent_nickname: agent_nickname.or(existing_agent_nickname),
agent_role: agent_role.or(existing_agent_role),
},
),
_ => source,
}
}
fn parse_datetime(timestamp: Option<&str>) -> Option<DateTime<Utc>> {
timestamp.and_then(|ts| {
chrono::DateTime::parse_from_rfc3339(ts)
@@ -6937,6 +6975,8 @@ fn build_thread_from_snapshot(
path,
cwd: config_snapshot.cwd.clone(),
cli_version: env!("CARGO_PKG_VERSION").to_string(),
agent_nickname: config_snapshot.session_source.get_nickname(),
agent_role: config_snapshot.session_source.get_agent_role(),
source: config_snapshot.session_source.clone().into(),
git_info: None,
turns: Vec::new(),
@@ -6975,6 +7015,8 @@ pub(crate) fn summary_to_thread(summary: ConversationSummary) -> Thread {
path: Some(path),
cwd,
cli_version,
agent_nickname: source.get_nickname(),
agent_role: source.get_agent_role(),
source: source.into(),
git_info,
turns: Vec::new(),
@@ -6986,6 +7028,7 @@ mod tests {
use super::*;
use anyhow::Result;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
@@ -7131,6 +7174,41 @@ mod tests {
Ok(())
}
#[test]
fn summary_from_state_db_metadata_preserves_agent_nickname() -> Result<()> {
let conversation_id = ThreadId::from_string("bfd12a78-5900-467b-9bc5-d3d35df08191")?;
let source =
serde_json::to_string(&SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id: ThreadId::from_string("ad7f0408-99b8-4f6e-a46f-bd0eec433370")?,
depth: 1,
agent_nickname: None,
agent_role: None,
}))?;
let summary = summary_from_state_db_metadata(
conversation_id,
PathBuf::from("/tmp/rollout.jsonl"),
Some("hi".to_string()),
"2025-09-05T16:53:11Z".to_string(),
"2025-09-05T16:53:12Z".to_string(),
"test-provider".to_string(),
PathBuf::from("/"),
"0.0.0".to_string(),
source,
Some("atlas".to_string()),
Some("explorer".to_string()),
None,
None,
None,
);
let thread = summary_to_thread(summary);
assert_eq!(thread.agent_nickname, Some("atlas".to_string()));
assert_eq!(thread.agent_role, Some("explorer".to_string()));
Ok(())
}
#[tokio::test]
async fn removing_one_listener_does_not_cancel_other_subscriptions_for_same_thread()
-> Result<()> {