diff --git a/src-tauri/recipes.json b/src-tauri/recipes.json index ca4956e..5d881b6 100644 --- a/src-tauri/recipes.json +++ b/src-tauri/recipes.json @@ -1,71 +1,42 @@ { "recipes": [ + { + "id": "dedicated-channel-agent", + "name": "Create dedicated Agent for Channel", + "description": "Create an independent agent, set its identity, bind it to a Discord channel, and configure persona", + "version": "1.0.0", + "tags": ["discord", "agent", "persona"], + "difficulty": "easy", + "params": [ + { "id": "agent_id", "label": "Agent ID", "type": "string", "required": true, "placeholder": "e.g. my-bot" }, + { "id": "guild_id", "label": "Guild", "type": "discord_guild", "required": true }, + { "id": "channel_id", "label": "Channel", "type": "discord_channel", "required": true }, + { "id": "name", "label": "Display Name", "type": "string", "required": true, "placeholder": "e.g. MyBot" }, + { "id": "emoji", "label": "Emoji", "type": "string", "required": false, "placeholder": "e.g. \ud83e\udd16" }, + { "id": "persona", "label": "Persona", "type": "textarea", "required": true, "placeholder": "You are..." } + ], + "steps": [ + { "action": "create_agent", "label": "Create independent agent", "args": { "agentId": "{{agent_id}}", "independent": true } }, + { "action": "setup_identity", "label": "Set agent identity", "args": { "agentId": "{{agent_id}}", "name": "{{name}}", "emoji": "{{emoji}}" } }, + { "action": "bind_channel", "label": "Bind channel to agent", "args": { "channelType": "discord", "peerId": "{{channel_id}}", "agentId": "{{agent_id}}" } }, + { "action": "config_patch", "label": "Set channel persona", "args": { "patchTemplate": "{\"channels\":{\"discord\":{\"guilds\":{\"{{guild_id}}\":{\"channels\":{\"{{channel_id}}\":{\"systemPrompt\":\"{{persona}}\"}}}}}}}" } } + ] + }, { "id": "discord-channel-persona", - "name": "Discord channel persona", - "description": "Inject different system prompt for one Discord channel", + "name": "Channel Persona", + "description": "Set a custom persona for a Discord channel", "version": "1.0.0", "tags": ["discord", "persona", "beginner"], "difficulty": "easy", "params": [ - { - "id": "guild_id", - "label": "Guild", - "type": "discord_guild", - "required": true - }, - { - "id": "channel_id", - "label": "Channel", - "type": "discord_channel", - "required": true - }, - { - "id": "persona", - "label": "Persona", - "type": "textarea", - "required": true, - "minLength": 1, - "placeholder": "You are..." - } + { "id": "guild_id", "label": "Guild", "type": "discord_guild", "required": true }, + { "id": "channel_id", "label": "Channel", "type": "discord_channel", "required": true }, + { "id": "persona", "label": "Persona", "type": "textarea", "required": true, "placeholder": "You are..." } ], - "patchTemplate": "\n{\n \"channels\": {\n \"discord\": {\n \"guilds\": {\n \"{{guild_id}}\": {\n \"channels\": {\n \"{{channel_id}}\": {\n \"systemPrompt\": \"{{persona}}\"\n }\n }\n }\n }\n }\n }\n}", - "impactCategory": "low", - "impactSummary": "Add/modify channel persona" - }, - { - "id": "setup-agent", - "name": "Setup agent identity", - "description": "Set the display name and emoji for an agent", - "version": "1.0.0", - "tags": ["agent", "identity", "beginner"], - "difficulty": "easy", - "action": "setup_agent", - "params": [ - { - "id": "agent_id", - "label": "Agent", - "type": "agent", - "required": true - }, - { - "id": "name", - "label": "Display Name", - "type": "string", - "required": true, - "placeholder": "e.g. Owlia" - }, - { - "id": "emoji", - "label": "Emoji", - "type": "string", - "required": false, - "placeholder": "e.g. 🦉" - } - ], - "patchTemplate": "", - "impactCategory": "low", - "impactSummary": "Set agent display name and emoji" + "steps": [ + { "action": "config_patch", "label": "Set channel persona", "args": { "patchTemplate": "{\"channels\":{\"discord\":{\"guilds\":{\"{{guild_id}}\":{\"channels\":{\"{{channel_id}}\":{\"systemPrompt\":\"{{persona}}\"}}}}}}}" } } + ] } ] } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b49b31f..7dae55f 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -11,12 +11,10 @@ use crate::models::resolve_paths; use crate::recipe::{ load_recipes_with_fallback, collect_change_paths, - build_candidate_config, - find_recipe_with_source, + build_candidate_config_from_template, format_diff, ApplyResult, PreviewResult, - validate, }; #[derive(Debug, Serialize, Deserialize)] @@ -1195,55 +1193,10 @@ pub fn list_recipes(source: Option) -> Result } #[tauri::command] -pub fn preview_apply( - recipe_id: String, +pub fn apply_config_patch( + patch_template: String, params: Map, - source: Option, -) -> Result { - let recipe = - find_recipe_with_source(&recipe_id, source).ok_or_else(|| "unknown recipe".to_string())?; - let errors = validate(&recipe, ¶ms); - if !errors.is_empty() { - return Err(format!("validation failed: {}", errors.join(","))); - } - let paths = resolve_paths(); - ensure_dirs(&paths)?; - let current = read_openclaw_config(&paths)?; - let (candidate, changes) = build_candidate_config(¤t, &recipe, ¶ms)?; - let before_text = serde_json::to_string_pretty(¤t).unwrap_or_else(|_| "{}".into()); - let after_text = serde_json::to_string_pretty(&candidate).unwrap_or_else(|_| "{}".into()); - Ok(PreviewResult { - recipe_id: recipe_id.clone(), - diff: format_diff(¤t, &candidate), - config_before: before_text, - config_after: after_text, - changes, - overwrites_existing: true, - can_rollback: true, - impact_level: recipe.impact_category, - warnings: Vec::new(), - }) -} - -#[tauri::command] -pub fn apply_recipe( - recipe_id: String, - params: Map, - source: Option, ) -> Result { - let recipe = - find_recipe_with_source(&recipe_id, source).ok_or_else(|| "unknown recipe".to_string())?; - let errors = validate(&recipe, ¶ms); - if !errors.is_empty() { - return Ok(ApplyResult { - ok: false, - snapshot_id: None, - config_path: String::new(), - backup_path: None, - warnings: errors, - errors: vec!["validation failed".into()], - }); - } let paths = resolve_paths(); ensure_dirs(&paths)?; let current = read_openclaw_config(&paths)?; @@ -1251,13 +1204,13 @@ pub fn apply_recipe( let snapshot = add_snapshot( &paths.history_dir, &paths.metadata_path, - Some(recipe_id.clone()), + Some("config-patch".into()), "apply", true, ¤t_text, None, )?; - let (candidate, _changes) = build_candidate_config(¤t, &recipe, ¶ms)?; + let (candidate, _changes) = build_candidate_config_from_template(¤t, &patch_template, ¶ms)?; write_json(&paths.config_path, &candidate)?; Ok(ApplyResult { ok: true, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c8afb22..aa2d7da 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,5 +1,5 @@ use crate::commands::{ - apply_recipe, fix_issues, get_system_status, get_status_light, list_history, list_recipes, preview_apply, + apply_config_patch, fix_issues, get_system_status, get_status_light, list_history, list_recipes, list_model_profiles, upsert_model_profile, delete_model_profile, list_model_catalog, get_cached_model_catalog, refresh_model_catalog, resolve_provider_auth, check_openclaw_update, extract_model_profiles_from_config, @@ -51,8 +51,7 @@ pub fn run() { clear_agent_sessions, check_openclaw_update, extract_model_profiles_from_config, - preview_apply, - apply_recipe, + apply_config_patch, list_history, preview_rollback, rollback, diff --git a/src-tauri/src/recipe.rs b/src-tauri/src/recipe.rs index fa24724..c467569 100644 --- a/src-tauri/src/recipe.rs +++ b/src-tauri/src/recipe.rs @@ -30,6 +30,14 @@ pub struct RecipeParam { pub placeholder: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RecipeStep { + pub action: String, + pub label: String, + pub args: Map, +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Recipe { @@ -40,9 +48,7 @@ pub struct Recipe { pub tags: Vec, pub difficulty: String, pub params: Vec, - pub patch_template: String, - pub impact_category: String, - pub impact_summary: String, + pub steps: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -218,22 +224,16 @@ fn render_patch_template(template: &str, params: &Map) -> String text } -pub fn build_candidate_config( +pub fn build_candidate_config_from_template( current: &Value, - recipe: &Recipe, + template: &str, params: &Map, ) -> Result<(Value, Vec), String> { - let rendered = render_patch_template(&recipe.patch_template, params); + let rendered = render_patch_template(template, params); let patch: Value = json5::from_str(&rendered).map_err(|e| e.to_string())?; - let mut merged = current.clone(); let mut changes = Vec::new(); apply_merge_patch(&mut merged, &patch, "", &mut changes); - if recipe.impact_category == "high" { - for change in &mut changes { - change.risk = "high".into(); - } - } Ok((merged, changes)) }