mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[TUI] add external config migration prompt when start TUI (#17891)
- add a TUI startup migration prompt for external agent config - support migrating external configs including config, skills, AGENTS.md and plugins - gate the prompt behind features.external_migrate (default false) <img width="1037" height="480" alt="Screenshot 2026-04-14 at 9 29 14 PM" src="https://github.com/user-attachments/assets/6060849b-03cb-429a-9c13-c7bb46ad2e65" /> <img width="713" height="183" alt="Screenshot 2026-04-14 at 9 29 26 PM" src="https://github.com/user-attachments/assets/d13f177e-d4c4-479c-8736-ef29636081e1" /> --------- Co-authored-by: Eric Traut <etraut@openai.com>
This commit is contained in:
co-authored by
Eric Traut
parent
370bed4bf4
commit
93ff798e5b
@@ -43,6 +43,14 @@ pub enum ConfigEdit {
|
||||
SetWindowsWslSetupAcknowledged(bool),
|
||||
/// Toggle the model migration prompt acknowledgement flag.
|
||||
SetNoticeHideModelMigrationPrompt(String, bool),
|
||||
/// Toggle the home external config migration prompt acknowledgement flag.
|
||||
SetNoticeHideExternalConfigMigrationPromptHome(bool),
|
||||
/// Record when the home external config migration prompt was last shown.
|
||||
SetNoticeExternalConfigMigrationPromptHomeLastPromptedAt(i64),
|
||||
/// Toggle the project external config migration prompt acknowledgement flag.
|
||||
SetNoticeHideExternalConfigMigrationPromptProject(String, bool),
|
||||
/// Record when the project external config migration prompt was last shown.
|
||||
SetNoticeExternalConfigMigrationPromptProjectLastPromptedAt(String, i64),
|
||||
/// Record that a migration prompt was shown for an old->new model mapping.
|
||||
RecordModelMigrationSeen { from: String, to: String },
|
||||
/// Replace the entire `[mcp_servers]` table.
|
||||
@@ -421,6 +429,53 @@ impl ConfigDocument {
|
||||
value(*acknowledged),
|
||||
))
|
||||
}
|
||||
ConfigEdit::SetNoticeHideExternalConfigMigrationPromptHome(acknowledged) => Ok(self
|
||||
.write_value(
|
||||
Scope::Global,
|
||||
&[
|
||||
NOTICE_TABLE_KEY,
|
||||
"external_config_migration_prompts",
|
||||
"home",
|
||||
],
|
||||
value(*acknowledged),
|
||||
)),
|
||||
ConfigEdit::SetNoticeExternalConfigMigrationPromptHomeLastPromptedAt(timestamp) => {
|
||||
Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&[
|
||||
NOTICE_TABLE_KEY,
|
||||
"external_config_migration_prompts",
|
||||
"home_last_prompted_at",
|
||||
],
|
||||
value(*timestamp),
|
||||
))
|
||||
}
|
||||
ConfigEdit::SetNoticeHideExternalConfigMigrationPromptProject(
|
||||
project,
|
||||
acknowledged,
|
||||
) => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&[
|
||||
NOTICE_TABLE_KEY,
|
||||
"external_config_migration_prompts",
|
||||
"projects",
|
||||
project.as_str(),
|
||||
],
|
||||
value(*acknowledged),
|
||||
)),
|
||||
ConfigEdit::SetNoticeExternalConfigMigrationPromptProjectLastPromptedAt(
|
||||
project,
|
||||
timestamp,
|
||||
) => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&[
|
||||
NOTICE_TABLE_KEY,
|
||||
"external_config_migration_prompts",
|
||||
"project_last_prompted_at",
|
||||
project.as_str(),
|
||||
],
|
||||
value(*timestamp),
|
||||
)),
|
||||
ConfigEdit::RecordModelMigrationSeen { from, to } => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
&[NOTICE_TABLE_KEY, "model_migrations", from.as_str()],
|
||||
@@ -919,6 +974,28 @@ impl ConfigEditsBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_hide_external_config_migration_prompt_home(mut self, acknowledged: bool) -> Self {
|
||||
self.edits
|
||||
.push(ConfigEdit::SetNoticeHideExternalConfigMigrationPromptHome(
|
||||
acknowledged,
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_hide_external_config_migration_prompt_project(
|
||||
mut self,
|
||||
project: &str,
|
||||
acknowledged: bool,
|
||||
) -> Self {
|
||||
self.edits.push(
|
||||
ConfigEdit::SetNoticeHideExternalConfigMigrationPromptProject(
|
||||
project.to_string(),
|
||||
acknowledged,
|
||||
),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn record_model_migration_seen(mut self, from: &str, to: &str) -> Self {
|
||||
self.edits.push(ConfigEdit::RecordModelMigrationSeen {
|
||||
from: from.to_string(),
|
||||
|
||||
@@ -552,6 +552,130 @@ gpt-5 = "gpt-5.1"
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_set_hide_external_config_migration_prompt_home_preserves_table() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[notice]
|
||||
existing = "value"
|
||||
"#,
|
||||
)
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeHideExternalConfigMigrationPromptHome(
|
||||
true,
|
||||
)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[notice]
|
||||
existing = "value"
|
||||
|
||||
[notice.external_config_migration_prompts]
|
||||
home = true
|
||||
"#;
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_set_hide_external_config_migration_prompt_project_preserves_table() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[notice]
|
||||
existing = "value"
|
||||
"#,
|
||||
)
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[
|
||||
ConfigEdit::SetNoticeHideExternalConfigMigrationPromptProject(
|
||||
"/Users/alexsong/code/skills".to_string(),
|
||||
true,
|
||||
),
|
||||
],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[notice]
|
||||
existing = "value"
|
||||
|
||||
[notice.external_config_migration_prompts.projects]
|
||||
"/Users/alexsong/code/skills" = true
|
||||
"#;
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_set_external_config_migration_prompt_home_last_prompted_at_preserves_table() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[notice]
|
||||
existing = "value"
|
||||
"#,
|
||||
)
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeExternalConfigMigrationPromptHomeLastPromptedAt(1_760_000_000)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[notice]
|
||||
existing = "value"
|
||||
|
||||
[notice.external_config_migration_prompts]
|
||||
home_last_prompted_at = 1760000000
|
||||
"#;
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_set_external_config_migration_prompt_project_last_prompted_at_preserves_table() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
let codex_home = tmp.path();
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[notice]
|
||||
existing = "value"
|
||||
"#,
|
||||
)
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[
|
||||
ConfigEdit::SetNoticeExternalConfigMigrationPromptProjectLastPromptedAt(
|
||||
"/Users/alexsong/code/skills".to_string(),
|
||||
1_760_000_000,
|
||||
),
|
||||
],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[notice]
|
||||
existing = "value"
|
||||
|
||||
[notice.external_config_migration_prompts.project_last_prompted_at]
|
||||
"/Users/alexsong/code/skills" = 1760000000
|
||||
"#;
|
||||
assert_eq!(contents, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_replace_mcp_servers_round_trips() {
|
||||
let tmp = tempdir().expect("tmpdir");
|
||||
|
||||
Reference in New Issue
Block a user