mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
Update built-in agent avatars (#2215)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -15,6 +15,7 @@
|
||||
//! discovery table. Ensures known providers always have their canonical
|
||||
//! `mcp_command`; unknown/custom agents are left untouched.
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tauri::Manager;
|
||||
|
||||
@@ -175,6 +176,7 @@ fn run_boot_migrations_inner(app: &tauri::AppHandle, reset_completed: bool) {
|
||||
// Post-fold readers of the runtime map (`load_persona_runtimes`) fall
|
||||
// back to the unified store's definitions.
|
||||
fold_personas_into_agent_store(app);
|
||||
refresh_builtin_agent_avatars(app);
|
||||
// B5: manufacture definitions for standalone agents AFTER the fold (so
|
||||
// pre-existing definition slugs are present for collision checks) and
|
||||
// before event sync republishes — the backfilled link is what flips the
|
||||
@@ -511,6 +513,243 @@ fn patch_json_records(
|
||||
}
|
||||
}
|
||||
|
||||
struct LegacyBuiltInAvatar<'a> {
|
||||
persona_id: &'a str,
|
||||
data_url_sha256: &'a str,
|
||||
// Catalog-created agents persist the blob descriptor for the PNG after
|
||||
// `sanitize_image_for_upload` re-encodes it, not the decoded source bytes.
|
||||
sanitized_media_sha256: &'a str,
|
||||
persona_content_hash: &'a str,
|
||||
}
|
||||
|
||||
const LEGACY_BUILTIN_AVATARS: &[LegacyBuiltInAvatar<'static>] = &[
|
||||
LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:fizz",
|
||||
data_url_sha256: "2771b8c9c46aa3c8ac1c4d2acfa23fa9ba35b79c4b1694554e923081e3b8b4d0",
|
||||
sanitized_media_sha256: "1a4964ff4cf6c499df1a77a941c211c7d1e7ef755f1c395bc9a3b0f2878114a6",
|
||||
persona_content_hash: "b36381d042c8eb5c786a1a692c7ba5a47ae129b9972a1473b64d8fe03f4817c1",
|
||||
},
|
||||
LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:honey",
|
||||
data_url_sha256: "1979e54ef77fc94ec688170bd74dade35c563e7fcc82bb0714c672dfb018eab9",
|
||||
sanitized_media_sha256: "0e0ed9a35d4050bdd290aa8138d5ab811f222549f6acc3cee40a7feb65933e1f",
|
||||
persona_content_hash: "9c9b6b11f1cdd56ba645de02213c562e59c3690bf3f217f74a85df8e6575fd06",
|
||||
},
|
||||
LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:bumble",
|
||||
data_url_sha256: "c08cf3b8b4c3f8721df6143367ababdebae8f913b9c654401ba74bb3d233655b",
|
||||
sanitized_media_sha256: "9f798c61f8965b80beb808f505feb0a5b33726545188ea8212cc9ab22d05f0b6",
|
||||
persona_content_hash: "544a73f9106a3c8848b0f308b7a8b6f95077ac8deccdb9ed5552caa833d66c95",
|
||||
},
|
||||
];
|
||||
|
||||
struct LegacyAvatarMatch<'a> {
|
||||
persona_id: String,
|
||||
metadata: &'a LegacyBuiltInAvatar<'a>,
|
||||
was_uploaded: bool,
|
||||
}
|
||||
|
||||
/// Refresh the prior seeded avatar on built-in definitions and linked agent
|
||||
/// instances while preserving any avatar the user customized. Matching by the
|
||||
/// exact data URL or content-addressed upload digest makes the migration
|
||||
/// idempotent and avoids relying on timestamps or other persona fields the
|
||||
/// user may also have edited.
|
||||
fn refresh_builtin_agent_avatars(app: &tauri::AppHandle) {
|
||||
let Ok(dir) = app.path().app_data_dir() else {
|
||||
return;
|
||||
};
|
||||
let path = dir.join("agents/managed-agents.json");
|
||||
if path.exists() {
|
||||
refresh_builtin_agent_avatars_in_file(
|
||||
&path,
|
||||
LEGACY_BUILTIN_AVATARS,
|
||||
&crate::util::now_iso(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_builtin_agent_avatars_in_file(
|
||||
path: &Path,
|
||||
legacy_avatars: &[LegacyBuiltInAvatar<'_>],
|
||||
now: &str,
|
||||
) {
|
||||
let Ok(contents) = std::fs::read_to_string(path) else {
|
||||
return;
|
||||
};
|
||||
let Ok(mut records) = serde_json::from_str::<Vec<serde_json::Value>>(&contents) else {
|
||||
eprintln!(
|
||||
"buzz-desktop: refresh-builtin-agent-avatars: invalid JSON in {}",
|
||||
path.display()
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
// Definitions must be migrated first so linked instances can advance from
|
||||
// the exact old persona hash to the exact new one. Only advance an instance
|
||||
// that was in sync before migration; a genuinely drifted instance keeps its
|
||||
// old source version and therefore keeps its out-of-date indicator.
|
||||
let mut persona_version_updates = std::collections::HashMap::new();
|
||||
let mut changed = false;
|
||||
for record in &mut records {
|
||||
let Some(legacy_match) = legacy_avatar_match(record, legacy_avatars) else {
|
||||
continue;
|
||||
};
|
||||
let persona_id = legacy_match.persona_id.clone();
|
||||
let is_definition = record
|
||||
.get("pubkey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(str::is_empty)
|
||||
&& record.get("slug").and_then(serde_json::Value::as_str) == Some(&persona_id);
|
||||
if !is_definition {
|
||||
continue;
|
||||
}
|
||||
let old_version = persona_version_from_record(record);
|
||||
if !replace_builtin_avatar(record, &persona_id, now) {
|
||||
continue;
|
||||
}
|
||||
changed = true;
|
||||
if let (Some(old_version), Some(new_version)) =
|
||||
(old_version, persona_version_from_record(record))
|
||||
{
|
||||
persona_version_updates.insert(persona_id, (old_version, new_version));
|
||||
}
|
||||
}
|
||||
|
||||
for record in &mut records {
|
||||
let Some(legacy_match) = legacy_avatar_match(record, legacy_avatars) else {
|
||||
continue;
|
||||
};
|
||||
let persona_id = legacy_match.persona_id.clone();
|
||||
let legacy_avatar = record
|
||||
.get("avatar_url")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::to_string);
|
||||
let is_linked_instance = record
|
||||
.get("pubkey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(|pubkey| !pubkey.is_empty())
|
||||
&& record.get("persona_id").and_then(serde_json::Value::as_str) == Some(&persona_id);
|
||||
if !is_linked_instance {
|
||||
continue;
|
||||
}
|
||||
let version_update = persona_version_updates
|
||||
.get(&persona_id)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
legacy_avatar.as_deref().and_then(|legacy_avatar| {
|
||||
built_in_persona_version_update(&legacy_match, legacy_avatar, now)
|
||||
})
|
||||
});
|
||||
if !replace_builtin_avatar(record, &persona_id, now) {
|
||||
continue;
|
||||
}
|
||||
changed = true;
|
||||
if let Some((old_version, new_version)) = version_update {
|
||||
let source_was_current = record
|
||||
.get("persona_source_version")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
== Some(old_version.as_str());
|
||||
if source_was_current {
|
||||
record["persona_source_version"] = serde_json::Value::String(new_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
if let Ok(bytes) = serde_json::to_vec_pretty(&records) {
|
||||
if let Err(e) = crate::managed_agents::atomic_write_json_restricted(path, &bytes) {
|
||||
eprintln!("buzz-desktop: refresh-builtin-agent-avatars: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_avatar_match<'a>(
|
||||
record: &serde_json::Value,
|
||||
legacy_avatars: &'a [LegacyBuiltInAvatar<'a>],
|
||||
) -> Option<LegacyAvatarMatch<'a>> {
|
||||
let persona_id = record
|
||||
.get("persona_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.or_else(|| record.get("slug").and_then(serde_json::Value::as_str))?;
|
||||
let metadata = legacy_avatars
|
||||
.iter()
|
||||
.find(|legacy| legacy.persona_id == persona_id)?;
|
||||
let current_avatar = record
|
||||
.get("avatar_url")
|
||||
.and_then(serde_json::Value::as_str)?;
|
||||
let matches_data_url =
|
||||
hex::encode(Sha256::digest(current_avatar.as_bytes())) == metadata.data_url_sha256;
|
||||
let matches_uploaded_media = uploaded_media_sha256(current_avatar)
|
||||
.is_some_and(|sha256| sha256 == metadata.sanitized_media_sha256);
|
||||
(matches_data_url || matches_uploaded_media).then(|| LegacyAvatarMatch {
|
||||
persona_id: persona_id.to_string(),
|
||||
metadata,
|
||||
was_uploaded: matches_uploaded_media,
|
||||
})
|
||||
}
|
||||
|
||||
fn uploaded_media_sha256(avatar_url: &str) -> Option<String> {
|
||||
let url = url::Url::parse(avatar_url).ok()?;
|
||||
if !matches!(url.scheme(), "http" | "https") {
|
||||
return None;
|
||||
}
|
||||
let filename = url.path_segments()?.next_back()?;
|
||||
let (sha256, extension) = filename.rsplit_once('.')?;
|
||||
(!extension.is_empty()
|
||||
&& sha256.len() == 64
|
||||
&& sha256.bytes().all(|byte| byte.is_ascii_hexdigit()))
|
||||
.then(|| sha256.to_string())
|
||||
}
|
||||
|
||||
fn persona_version_from_record(record: &serde_json::Value) -> Option<String> {
|
||||
let record: crate::managed_agents::ManagedAgentRecord =
|
||||
serde_json::from_value(record.clone()).ok()?;
|
||||
let definition = record.to_definition_view()?;
|
||||
Some(crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(&definition),
|
||||
))
|
||||
}
|
||||
|
||||
fn built_in_persona_version_update(
|
||||
legacy_match: &LegacyAvatarMatch<'_>,
|
||||
legacy_avatar: &str,
|
||||
now: &str,
|
||||
) -> Option<(String, String)> {
|
||||
let mut current =
|
||||
crate::managed_agents::built_in_persona_definition(&legacy_match.persona_id, now)?;
|
||||
let new_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(¤t),
|
||||
);
|
||||
let old_version = if legacy_match.was_uploaded {
|
||||
legacy_match.metadata.persona_content_hash.to_string()
|
||||
} else {
|
||||
current.avatar_url = Some(legacy_avatar.to_string());
|
||||
crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(¤t),
|
||||
)
|
||||
};
|
||||
Some((old_version, new_version))
|
||||
}
|
||||
|
||||
fn replace_builtin_avatar(record: &mut serde_json::Value, persona_id: &str, now: &str) -> bool {
|
||||
let Some(replacement) = crate::managed_agents::built_in_persona_avatar_url(persona_id) else {
|
||||
return false;
|
||||
};
|
||||
let Some(record) = record.as_object_mut() else {
|
||||
return false;
|
||||
};
|
||||
record.insert(
|
||||
"avatar_url".to_string(),
|
||||
serde_json::Value::String(replacement.to_string()),
|
||||
);
|
||||
record.insert(
|
||||
"updated_at".to_string(),
|
||||
serde_json::Value::String(now.to_string()),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
/// Create symlinks for shared agent data files from the current (worktree)
|
||||
/// data directory to the canonical dev data directory.
|
||||
///
|
||||
@@ -1142,6 +1381,10 @@ mod test_support;
|
||||
#[path = "migration_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "migration_avatar_tests.rs"]
|
||||
mod avatar_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "migration_command_tests.rs"]
|
||||
mod command_tests;
|
||||
|
||||
296
desktop/src-tauri/src/migration_avatar_tests.rs
Normal file
296
desktop/src-tauri/src/migration_avatar_tests.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn refresh_builtin_agent_avatars_updates_seeded_values_and_preserves_customizations() {
|
||||
use sha2::{Digest as _, Sha256};
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("managed-agents.json");
|
||||
let old_fizz = "data:image/png;base64,old-fizz";
|
||||
let old_honey = "data:image/png;base64,old-honey";
|
||||
let fizz_hash = hex::encode(Sha256::digest(old_fizz.as_bytes()));
|
||||
let honey_hash = hex::encode(Sha256::digest(old_honey.as_bytes()));
|
||||
let legacy_avatars = [
|
||||
LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:fizz",
|
||||
data_url_sha256: fizz_hash.as_str(),
|
||||
sanitized_media_sha256: "",
|
||||
persona_content_hash: "",
|
||||
},
|
||||
LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:honey",
|
||||
data_url_sha256: honey_hash.as_str(),
|
||||
sanitized_media_sha256: "",
|
||||
persona_content_hash: "",
|
||||
},
|
||||
];
|
||||
let definition = crate::managed_agents::AgentDefinition {
|
||||
id: "builtin:fizz".to_string(),
|
||||
display_name: "Fizz".to_string(),
|
||||
avatar_url: Some(old_fizz.to_string()),
|
||||
system_prompt: "A customized built-in prompt".to_string(),
|
||||
runtime: Some("goose".to_string()),
|
||||
model: Some("test-model".to_string()),
|
||||
provider: Some("test-provider".to_string()),
|
||||
name_pool: vec!["Fizzy".to_string()],
|
||||
is_builtin: true,
|
||||
is_active: true,
|
||||
source_team: None,
|
||||
source_team_persona_slug: None,
|
||||
env_vars: Default::default(),
|
||||
respond_to: None,
|
||||
respond_to_allowlist: Vec::new(),
|
||||
parallelism: None,
|
||||
created_at: "before".to_string(),
|
||||
updated_at: "before".to_string(),
|
||||
};
|
||||
let old_persona_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(&definition),
|
||||
);
|
||||
let mut definition_record = serde_json::to_value(definition.into_agent_record()).unwrap();
|
||||
definition_record["future_definition_field"] = serde_json::json!("preserved");
|
||||
|
||||
let instance =
|
||||
|pubkey: &str, persona_id: &str, avatar_url: &str, persona_source_version: &str| {
|
||||
serde_json::json!({
|
||||
"name": pubkey,
|
||||
"pubkey": pubkey,
|
||||
"persona_id": persona_id,
|
||||
"relay_url": "ws://localhost:3000",
|
||||
"avatar_url": avatar_url,
|
||||
"acp_command": "buzz-acp",
|
||||
"agent_command": "goose",
|
||||
"agent_args": [],
|
||||
"mcp_command": "",
|
||||
"turn_timeout_seconds": 320,
|
||||
"parallelism": 4,
|
||||
"system_prompt": "A customized built-in prompt",
|
||||
"model": "test-model",
|
||||
"provider": "test-provider",
|
||||
"persona_source_version": persona_source_version,
|
||||
"env_vars": {},
|
||||
"start_on_app_launch": true,
|
||||
"created_at": "before",
|
||||
"updated_at": "before",
|
||||
"last_started_at": null,
|
||||
"last_stopped_at": null,
|
||||
"last_exit_code": null,
|
||||
"last_error": null
|
||||
})
|
||||
};
|
||||
let mut synced_instance = instance(
|
||||
"fizz-instance",
|
||||
"builtin:fizz",
|
||||
old_fizz,
|
||||
&old_persona_version,
|
||||
);
|
||||
synced_instance["future_instance_field"] = serde_json::json!("preserved");
|
||||
let records = serde_json::Value::Array(vec![
|
||||
definition_record,
|
||||
synced_instance,
|
||||
instance(
|
||||
"drifted-fizz-instance",
|
||||
"builtin:fizz",
|
||||
old_fizz,
|
||||
"genuinely-drifted-version",
|
||||
),
|
||||
instance(
|
||||
"honey-instance",
|
||||
"builtin:honey",
|
||||
"data:image/png;base64,user-customized",
|
||||
"honey-version",
|
||||
),
|
||||
instance("custom-instance", "custom:fizz", old_fizz, "custom-version"),
|
||||
]);
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&records).unwrap()).unwrap();
|
||||
|
||||
refresh_builtin_agent_avatars_in_file(&path, &legacy_avatars, "after");
|
||||
|
||||
let migrated: Vec<serde_json::Value> =
|
||||
serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
|
||||
let new_fizz = crate::managed_agents::built_in_persona_avatar_url("builtin:fizz").unwrap();
|
||||
assert_eq!(migrated[0]["avatar_url"], new_fizz);
|
||||
assert_eq!(migrated[0]["updated_at"], "after");
|
||||
assert_eq!(migrated[0]["future_definition_field"], "preserved");
|
||||
let migrated_definition: crate::managed_agents::ManagedAgentRecord =
|
||||
serde_json::from_value(migrated[0].clone()).unwrap();
|
||||
let new_persona_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(
|
||||
&migrated_definition.to_definition_view().unwrap(),
|
||||
),
|
||||
);
|
||||
assert_ne!(new_persona_version, old_persona_version);
|
||||
assert_eq!(migrated[1]["avatar_url"], new_fizz);
|
||||
assert_eq!(migrated[1]["updated_at"], "after");
|
||||
assert_eq!(migrated[1]["persona_source_version"], new_persona_version);
|
||||
assert_eq!(migrated[1]["future_instance_field"], "preserved");
|
||||
assert_eq!(migrated[2]["avatar_url"], new_fizz);
|
||||
assert_eq!(migrated[2]["updated_at"], "after");
|
||||
assert_eq!(
|
||||
migrated[2]["persona_source_version"],
|
||||
"genuinely-drifted-version"
|
||||
);
|
||||
assert_eq!(
|
||||
migrated[3]["avatar_url"],
|
||||
"data:image/png;base64,user-customized"
|
||||
);
|
||||
assert_eq!(migrated[3]["updated_at"], "before");
|
||||
assert_eq!(migrated[4]["avatar_url"], old_fizz);
|
||||
assert_eq!(migrated[4]["updated_at"], "before");
|
||||
|
||||
let once = std::fs::read(&path).unwrap();
|
||||
refresh_builtin_agent_avatars_in_file(&path, &legacy_avatars, "later");
|
||||
assert_eq!(std::fs::read(&path).unwrap(), once);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_builtin_agent_avatars_do_not_match_legacy_hashes() {
|
||||
use sha2::{Digest as _, Sha256};
|
||||
|
||||
for legacy in LEGACY_BUILTIN_AVATARS {
|
||||
let current =
|
||||
crate::managed_agents::built_in_persona_avatar_url(legacy.persona_id).unwrap();
|
||||
assert_ne!(
|
||||
hex::encode(Sha256::digest(current.as_bytes())),
|
||||
legacy.data_url_sha256
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_builtin_agent_avatars_updates_versions_without_stored_definitions() {
|
||||
use sha2::{Digest as _, Sha256};
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("managed-agents.json");
|
||||
let old_fizz = "data:image/png;base64,old-fizz";
|
||||
let fizz_hash = hex::encode(Sha256::digest(old_fizz.as_bytes()));
|
||||
let mut old_definition =
|
||||
crate::managed_agents::built_in_persona_definition("builtin:fizz", "before").unwrap();
|
||||
old_definition.avatar_url = Some(old_fizz.to_string());
|
||||
let old_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(&old_definition),
|
||||
);
|
||||
let legacy_avatars = [LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:fizz",
|
||||
data_url_sha256: fizz_hash.as_str(),
|
||||
sanitized_media_sha256: "",
|
||||
persona_content_hash: old_version.as_str(),
|
||||
}];
|
||||
let records = serde_json::json!([
|
||||
{
|
||||
"name": "synced-fizz",
|
||||
"pubkey": "synced-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": old_fizz,
|
||||
"persona_source_version": old_version,
|
||||
"updated_at": "before"
|
||||
},
|
||||
{
|
||||
"name": "drifted-fizz",
|
||||
"pubkey": "drifted-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": old_fizz,
|
||||
"persona_source_version": "genuinely-drifted-version",
|
||||
"updated_at": "before"
|
||||
}
|
||||
]);
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&records).unwrap()).unwrap();
|
||||
|
||||
refresh_builtin_agent_avatars_in_file(&path, &legacy_avatars, "after");
|
||||
|
||||
let migrated: Vec<serde_json::Value> =
|
||||
serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
|
||||
let current_definition =
|
||||
crate::managed_agents::built_in_persona_definition("builtin:fizz", "after").unwrap();
|
||||
let current_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(¤t_definition),
|
||||
);
|
||||
let new_fizz = crate::managed_agents::built_in_persona_avatar_url("builtin:fizz").unwrap();
|
||||
assert_eq!(migrated[0]["avatar_url"], new_fizz);
|
||||
assert_eq!(migrated[0]["persona_source_version"], current_version);
|
||||
assert_eq!(migrated[0]["updated_at"], "after");
|
||||
assert_eq!(migrated[1]["avatar_url"], new_fizz);
|
||||
assert_eq!(
|
||||
migrated[1]["persona_source_version"],
|
||||
"genuinely-drifted-version"
|
||||
);
|
||||
assert_eq!(migrated[1]["updated_at"], "after");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_builtin_agent_avatars_updates_uploaded_media_urls() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("managed-agents.json");
|
||||
let media_sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let different_sha256 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||
let old_persona_version = "legacy-persona-version";
|
||||
let legacy_avatars = [LegacyBuiltInAvatar {
|
||||
persona_id: "builtin:fizz",
|
||||
data_url_sha256: "not-a-data-url-hash",
|
||||
sanitized_media_sha256: media_sha256,
|
||||
persona_content_hash: old_persona_version,
|
||||
}];
|
||||
let matching_url = format!("https://relay.example/media/{media_sha256}.png?download=1");
|
||||
let custom_url = format!("https://relay.example/media/{different_sha256}.png");
|
||||
let embedded_hash_url = format!("https://relay.example/media/avatar-{media_sha256}.png");
|
||||
let records = serde_json::json!([
|
||||
{
|
||||
"name": "synced-fizz",
|
||||
"pubkey": "synced-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": matching_url,
|
||||
"persona_source_version": old_persona_version,
|
||||
"updated_at": "before"
|
||||
},
|
||||
{
|
||||
"name": "drifted-fizz",
|
||||
"pubkey": "drifted-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": matching_url,
|
||||
"persona_source_version": "genuinely-drifted-version",
|
||||
"updated_at": "before"
|
||||
},
|
||||
{
|
||||
"name": "custom-fizz",
|
||||
"pubkey": "custom-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": custom_url,
|
||||
"persona_source_version": old_persona_version,
|
||||
"updated_at": "before"
|
||||
},
|
||||
{
|
||||
"name": "embedded-hash-fizz",
|
||||
"pubkey": "embedded-hash-fizz",
|
||||
"persona_id": "builtin:fizz",
|
||||
"avatar_url": embedded_hash_url,
|
||||
"persona_source_version": old_persona_version,
|
||||
"updated_at": "before"
|
||||
}
|
||||
]);
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&records).unwrap()).unwrap();
|
||||
|
||||
refresh_builtin_agent_avatars_in_file(&path, &legacy_avatars, "after");
|
||||
|
||||
let migrated: Vec<serde_json::Value> =
|
||||
serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
|
||||
let current_definition =
|
||||
crate::managed_agents::built_in_persona_definition("builtin:fizz", "after").unwrap();
|
||||
let current_version = crate::managed_agents::persona_events::persona_content_hash(
|
||||
&crate::managed_agents::persona_events::persona_event_content(¤t_definition),
|
||||
);
|
||||
let new_fizz = crate::managed_agents::built_in_persona_avatar_url("builtin:fizz").unwrap();
|
||||
assert_eq!(migrated[0]["avatar_url"], new_fizz);
|
||||
assert_eq!(migrated[0]["persona_source_version"], current_version);
|
||||
assert_eq!(migrated[0]["updated_at"], "after");
|
||||
assert_eq!(migrated[1]["avatar_url"], new_fizz);
|
||||
assert_eq!(
|
||||
migrated[1]["persona_source_version"],
|
||||
"genuinely-drifted-version"
|
||||
);
|
||||
assert_eq!(migrated[1]["updated_at"], "after");
|
||||
assert_eq!(migrated[2]["avatar_url"], custom_url);
|
||||
assert_eq!(migrated[2]["updated_at"], "before");
|
||||
assert_eq!(migrated[3]["avatar_url"], embedded_hash_url);
|
||||
assert_eq!(migrated[3]["updated_at"], "before");
|
||||
}
|
||||
Reference in New Issue
Block a user