From 6648c4bfbc2a3222f93322f187d574fc4ae0955a Mon Sep 17 00:00:00 2001 From: john Date: Thu, 10 Sep 2026 12:34:54 +0800 Subject: [PATCH] fix(tkmind): restore provider and session recipe on compat reply paths Add POST /agent/update_from_session to apply session recipe to the agent, and restore provider from session when session_reply finds no provider set. Co-authored-by: Cursor --- crates/goose/src/acp/tkmind/agent.rs | 84 +++++++++++++++++++ crates/goose/src/acp/tkmind/session_events.rs | 18 ++++ 2 files changed, 102 insertions(+) diff --git a/crates/goose/src/acp/tkmind/agent.rs b/crates/goose/src/acp/tkmind/agent.rs index d6c4c88cc..ecb0b2e8a 100644 --- a/crates/goose/src/acp/tkmind/agent.rs +++ b/crates/goose/src/acp/tkmind/agent.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -14,6 +15,7 @@ use serde_json::Value; use tokio_util::sync::CancellationToken; use tracing::error; +use crate::agents::Agent; use crate::agents::extension::ToolInfo; use crate::agents::extension_manager::get_parameter_names; use crate::agents::reply_parts::is_tool_visible_to_app; @@ -25,6 +27,8 @@ use crate::config::permission::PermissionLevel; use crate::config::resolve_extensions_for_new_session; use crate::config::{Config, GooseMode}; use crate::providers::create; +use crate::recipe::build_recipe::{build_recipe_from_template, RecipeError}; +use crate::recipe::local_recipes::get_recipe_library_dir; use crate::recipe::Recipe; use crate::session::session_manager::SessionType; use crate::session::{EnabledExtensionsState, ExtensionState, Session}; @@ -71,6 +75,11 @@ pub struct UpdateWorkingDirRequest { working_dir: String, } +#[derive(Deserialize)] +pub struct UpdateFromSessionRequest { + session_id: String, +} + #[derive(Deserialize)] pub struct RestartAgentRequest { session_id: String, @@ -658,6 +667,80 @@ async fn resume_agent( })) } +async fn build_recipe_with_parameter_values( + original_recipe: &Recipe, + user_recipe_values: HashMap, +) -> Result, String> { + let recipe_content = original_recipe + .to_yaml() + .map_err(|err| format!("Failed to serialize recipe: {err}"))?; + let recipe_dir = get_recipe_library_dir(true); + let params: Vec<(String, String)> = user_recipe_values.into_iter().collect(); + match build_recipe_from_template( + recipe_content, + &recipe_dir, + params, + None:: Result>, + ) { + Ok(recipe) => Ok(Some(recipe)), + Err(RecipeError::MissingParams { .. }) => Ok(None), + Err(err) => Err(err.to_string()), + } +} + +async fn apply_recipe_to_agent(agent: &Arc, recipe: &Recipe) { + let _ = agent + .apply_recipe_components(recipe.response.clone(), true) + .await; + if let Some(instructions) = recipe.instructions.as_ref() { + agent + .extend_system_prompt("recipe".to_string(), instructions.clone()) + .await; + } +} + +async fn apply_session_recipe_to_agent( + agent: &Arc, + session: &Session, +) -> Result<(), String> { + let Some(recipe) = session.recipe.as_ref() else { + return Ok(()); + }; + + if session.session_type == SessionType::Scheduled { + apply_recipe_to_agent(agent, recipe).await; + return Ok(()); + } + + let user_values = session.user_recipe_values.clone().unwrap_or_default(); + match build_recipe_with_parameter_values(recipe, user_values).await? { + Some(rendered) => apply_recipe_to_agent(agent, &rendered).await, + None => {} + } + Ok(()) +} + +async fn update_from_session( + State(state): State>, + Json(payload): Json, +) -> Result { + let agent = state + .get_agent_for_route(payload.session_id.clone()) + .await + .map_err(|status| (status, "No agent for session id".to_owned()))?; + let session = state + .session_manager() + .get_session(&payload.session_id, false) + .await + .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get session: {err}")))?; + + apply_session_recipe_to_agent(&agent, &session) + .await + .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err))?; + + Ok(StatusCode::OK) +} + pub fn routes(state: Arc) -> Router { Router::new() .route("/agent/start", post(start_agent)) @@ -665,6 +748,7 @@ pub fn routes(state: Arc) -> Router { .route("/agent/call_tool", post(call_tool)) .route("/agent/tools", get(get_tools)) .route("/agent/update_provider", post(update_agent_provider)) + .route("/agent/update_from_session", post(update_from_session)) .route("/agent/update_session", post(update_session)) .route("/agent/add_extension", post(add_extension)) .route("/agent/remove_extension", post(remove_extension)) diff --git a/crates/goose/src/acp/tkmind/session_events.rs b/crates/goose/src/acp/tkmind/session_events.rs index ac140978f..f878acc01 100644 --- a/crates/goose/src/acp/tkmind/session_events.rs +++ b/crates/goose/src/acp/tkmind/session_events.rs @@ -347,6 +347,24 @@ pub async fn session_reply( } }; + if agent.provider().await.is_err() { + if let Err(error) = agent.restore_provider_from_session(&session).await { + tracing::error!( + "Failed to restore provider for session {}: {}", + task_session_id, + error + ); + publish( + Some(task_request_id.clone()), + MessageEvent::Error { + error: format!("Provider not set: {}", error), + }, + ) + .await; + return; + } + } + let session_config = SessionConfig { id: task_session_id.clone(), schedule_id: session.schedule_id.clone(),