-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat: platform extension migrator + code mode rename #6611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -273,25 +273,34 @@ impl Config { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| fn load(&self) -> Result<Mapping, ConfigError> { | ||||||||||||||||||||||||||
| if self.config_path.exists() { | ||||||||||||||||||||||||||
| self.load_values_with_recovery() | ||||||||||||||||||||||||||
| let mut values = if self.config_path.exists() { | ||||||||||||||||||||||||||
| self.load_values_with_recovery()? | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| // Config file doesn't exist, try to recover from backup first | ||||||||||||||||||||||||||
| tracing::info!("Config file doesn't exist, attempting recovery from backup"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if let Ok(backup_values) = self.try_restore_from_backup() { | ||||||||||||||||||||||||||
| tracing::info!("Successfully restored config from backup"); | ||||||||||||||||||||||||||
| return Ok(backup_values); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| backup_values | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| // No backup available, create a default config | ||||||||||||||||||||||||||
| tracing::info!("No backup found, creating default configuration"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // No backup available, create a default config | ||||||||||||||||||||||||||
| tracing::info!("No backup found, creating default configuration"); | ||||||||||||||||||||||||||
| // Try to load from init-config.yaml if it exists, otherwise use empty config | ||||||||||||||||||||||||||
| let default_config = self.load_init_config_if_exists().unwrap_or_default(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Try to load from init-config.yaml if it exists, otherwise use empty config | ||||||||||||||||||||||||||
| let default_config = self.load_init_config_if_exists().unwrap_or_default(); | ||||||||||||||||||||||||||
| self.create_and_save_default_config(default_config)? | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| self.create_and_save_default_config(default_config) | ||||||||||||||||||||||||||
| // Run migrations on the loaded config | ||||||||||||||||||||||||||
| if crate::config::migrations::run_migrations(&mut values) { | ||||||||||||||||||||||||||
| if let Err(e) = self.save_values(values.clone()) { | ||||||||||||||||||||||||||
| tracing::warn!("Failed to save migrated config: {}", e); | ||||||||||||||||||||||||||
|
Comment on lines
+298
to
+299
|
||||||||||||||||||||||||||
| if let Err(e) = self.save_values(values.clone()) { | |
| tracing::warn!("Failed to save migrated config: {}", e); | |
| // Persist migrated config under the same guard used by other writers | |
| match self.guard.lock() { | |
| Ok(_guard) => { | |
| if let Err(e) = self.save_values(values.clone()) { | |
| tracing::warn!("Failed to save migrated config: {}", e); | |
| } | |
| } | |
| Err(e) => { | |
| tracing::warn!("Failed to acquire config lock for migrated save: {}", e); | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| use crate::agents::extension::PLATFORM_EXTENSIONS; | ||
| use crate::agents::ExtensionConfig; | ||
| use crate::config::extensions::ExtensionEntry; | ||
| use serde_yaml::Mapping; | ||
|
|
||
| const EXTENSIONS_CONFIG_KEY: &str = "extensions"; | ||
|
|
||
| pub fn run_migrations(config: &mut Mapping) -> bool { | ||
| let mut changed = false; | ||
| changed |= migrate_platform_extensions(config); | ||
| changed | ||
| } | ||
|
Comment on lines
+8
to
+12
|
||
|
|
||
| fn migrate_platform_extensions(config: &mut Mapping) -> bool { | ||
| let extensions_key = serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string()); | ||
|
|
||
| let extensions_value = config | ||
| .get(&extensions_key) | ||
| .cloned() | ||
| .unwrap_or(serde_yaml::Value::Mapping(Mapping::new())); | ||
|
|
||
| let mut extensions_map: Mapping = match extensions_value { | ||
| serde_yaml::Value::Mapping(m) => m, | ||
| _ => Mapping::new(), | ||
| }; | ||
|
|
||
| let mut needs_save = false; | ||
|
|
||
| for (name, def) in PLATFORM_EXTENSIONS.iter() { | ||
| let ext_key = serde_yaml::Value::String(name.to_string()); | ||
| let existing = extensions_map.get(&ext_key); | ||
|
|
||
| let needs_migration = match existing { | ||
| None => true, | ||
| Some(value) => match serde_yaml::from_value::<ExtensionEntry>(value.clone()) { | ||
| Ok(entry) => { | ||
| if let ExtensionConfig::Platform { | ||
| description, | ||
| display_name, | ||
| .. | ||
| } = &entry.config | ||
| { | ||
| description != def.description | ||
| || display_name.as_deref() != Some(def.display_name) | ||
| } else { | ||
| true | ||
| } | ||
| } | ||
| Err(_) => true, | ||
| }, | ||
| }; | ||
|
|
||
| if needs_migration { | ||
| let enabled = existing | ||
| .and_then(|v| serde_yaml::from_value::<ExtensionEntry>(v.clone()).ok()) | ||
| .map(|e| e.enabled) | ||
| .unwrap_or(def.default_enabled); | ||
|
|
||
| let new_entry = ExtensionEntry { | ||
| config: ExtensionConfig::Platform { | ||
| name: def.name.to_string(), | ||
| description: def.description.to_string(), | ||
| display_name: Some(def.display_name.to_string()), | ||
| bundled: Some(true), | ||
| available_tools: Vec::new(), | ||
alexhancock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| enabled, | ||
| }; | ||
|
|
||
| if let Ok(value) = serde_yaml::to_value(&new_entry) { | ||
| extensions_map.insert(ext_key, value); | ||
| needs_save = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if needs_save { | ||
| config.insert(extensions_key, serde_yaml::Value::Mapping(extensions_map)); | ||
| } | ||
|
|
||
| needs_save | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_migrate_platform_extensions_empty_config() { | ||
| let mut config = Mapping::new(); | ||
| let changed = run_migrations(&mut config); | ||
|
|
||
| assert!(changed); | ||
| let extensions_key = serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string()); | ||
| assert!(config.contains_key(&extensions_key)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_migrate_platform_extensions_preserves_enabled_state() { | ||
| let mut config = Mapping::new(); | ||
| let mut extensions = Mapping::new(); | ||
| let todo_entry = ExtensionEntry { | ||
| config: ExtensionConfig::Platform { | ||
| name: "todo".to_string(), | ||
| description: "old description".to_string(), | ||
| display_name: Some("Old Name".to_string()), | ||
| bundled: Some(true), | ||
| available_tools: Vec::new(), | ||
| }, | ||
| enabled: false, | ||
| }; | ||
| extensions.insert( | ||
| serde_yaml::Value::String("todo".to_string()), | ||
| serde_yaml::to_value(&todo_entry).unwrap(), | ||
| ); | ||
| config.insert( | ||
| serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string()), | ||
| serde_yaml::Value::Mapping(extensions), | ||
| ); | ||
|
|
||
| let changed = run_migrations(&mut config); | ||
| assert!(changed); | ||
|
|
||
| let extensions_key = serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string()); | ||
| let extensions = config.get(&extensions_key).unwrap().as_mapping().unwrap(); | ||
| let todo_key = serde_yaml::Value::String("todo".to_string()); | ||
| let todo_value = extensions.get(&todo_key).unwrap(); | ||
| let todo_entry: ExtensionEntry = serde_yaml::from_value(todo_value.clone()).unwrap(); | ||
|
|
||
| assert!(!todo_entry.enabled); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_migrate_platform_extensions_idempotent() { | ||
| let mut config = Mapping::new(); | ||
| run_migrations(&mut config); | ||
|
|
||
| let changed = run_migrations(&mut config); | ||
| assert!(!changed); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description mentions adding per-extension
versionand version-driven migrations, but the platform extension definition/config only addsdisplay_name(noversionfield), so migrations are currently based on field diffs; either implement theversionfield end-to-end (schema + config + migration logic) or update the PR description to match the actual behavior.