diff --git a/.refact/integrations.d/cmdline_cargo_check.yaml b/.refact/integrations.d/cmdline_cargo_check.yaml index 7dbe55210..7e1894490 100644 --- a/.refact/integrations.d/cmdline_cargo_check.yaml +++ b/.refact/integrations.d/cmdline_cargo_check.yaml @@ -5,7 +5,7 @@ parameters: - name: "additional_params" description: "Additional parameters for cargo check, such as --workspace or --all-features" - name: "project_path" - description: "absolute path to the project" + description: "Absolute path to the project, the rust stuff is at refact/refact-agent/engine/Cargo.toml for the Refact project, so use ../refact/refact-agent/engine" timeout: "60" output_filter: limit_lines: 100 diff --git a/refact-agent/engine/Cargo.toml b/refact-agent/engine/Cargo.toml index 35eca5673..536970b19 100644 --- a/refact-agent/engine/Cargo.toml +++ b/refact-agent/engine/Cargo.toml @@ -96,7 +96,6 @@ uuid = { version = "1", features = ["v4", "serde"] } walkdir = "2.3" which = "7.0.1" zerocopy = "0.8.14" -jsonschema = "0.28.3" # There you can use a local copy: mcp_client_rs = { git = "https://github.com/smallcloudai/mcp_client_rust.git" } diff --git a/refact-agent/engine/src/files_blocklist.rs b/refact-agent/engine/src/files_blocklist.rs index c998b5868..c0136cc81 100644 --- a/refact-agent/engine/src/files_blocklist.rs +++ b/refact-agent/engine/src/files_blocklist.rs @@ -198,7 +198,7 @@ pub async fn reload_indexing_everywhere_if_needed(gcx: Arc bool { let block = any_glob_matches_path(&indexing_settings.blocklist, &path); - tracing::info!("is_blocklisted {:?} {:?} block={}", indexing_settings, path, block); + // tracing::info!("is_blocklisted {:?} {:?} block={}", indexing_settings, path, block); block } diff --git a/refact-agent/engine/src/http/routers/v1.rs b/refact-agent/engine/src/http/routers/v1.rs index 0c9d08cda..8d8925fb1 100644 --- a/refact-agent/engine/src/http/routers/v1.rs +++ b/refact-agent/engine/src/http/routers/v1.rs @@ -43,7 +43,7 @@ use crate::http::routers::v1::system_prompt::handle_v1_prepend_system_prompt_and use crate::http::routers::v1::vecdb::{handle_v1_vecdb_search, handle_v1_vecdb_status}; #[cfg(feature="vecdb")] use crate::http::routers::v1::handlers_memdb::{handle_mem_add, handle_mem_erase, handle_mem_update_used, handle_mem_block_until_vectorized}; -use crate::http::routers::v1::v1_integrations::{handle_v1_integration_get, handle_v1_integration_icon, handle_v1_integration_save, handle_v1_integration_delete, handle_v1_integrations, handle_v1_integrations_filtered, handle_v1_integration_json_schema}; +use crate::http::routers::v1::v1_integrations::{handle_v1_integration_get, handle_v1_integration_icon, handle_v1_integration_save, handle_v1_integration_delete, handle_v1_integrations, handle_v1_integrations_filtered}; use crate::agent_db::db_cthread::{handle_db_v1_cthread_update, handle_db_v1_cthreads_sub}; use crate::agent_db::db_cmessage::{handle_db_v1_cmessages_update, handle_db_v1_cmessages_sub}; use crate::agent_db::db_chore::{handle_db_v1_chore_update, handle_db_v1_chore_event_update, handle_db_v1_chores_sub}; @@ -137,7 +137,6 @@ pub fn make_v1_router() -> Router { .route("/integration-save", telemetry_post!(handle_v1_integration_save)) .route("/integration-delete", delete(handle_v1_integration_delete)) .route("/integration-icon/:icon_name", get(handle_v1_integration_icon)) - .route("/integration-json-schema", get(handle_v1_integration_json_schema)) .route("/docker-container-list", telemetry_post!(handle_v1_docker_container_list)) .route("/docker-container-action", telemetry_post!(handle_v1_docker_container_action)) diff --git a/refact-agent/engine/src/http/routers/v1/v1_integrations.rs b/refact-agent/engine/src/http/routers/v1/v1_integrations.rs index 23be5ddd9..ba4ea8e4a 100644 --- a/refact-agent/engine/src/http/routers/v1/v1_integrations.rs +++ b/refact-agent/engine/src/http/routers/v1/v1_integrations.rs @@ -11,7 +11,6 @@ use axum::extract::Query; use rust_embed::RustEmbed; use crate::custom_error::ScratchError; use crate::global_context::GlobalContext; -use crate::integrations::json_schema::INTEGRATION_JSON_SCHEMA; use crate::integrations::setting_up_integrations::split_path_into_project_and_integration; @@ -189,14 +188,3 @@ pub async fn handle_v1_integration_delete( .body(Body::from("{}")) .unwrap()) } - -pub async fn handle_v1_integration_json_schema() -> axum::response::Result, ScratchError> { - let schema_string = serde_json::to_string_pretty(&*INTEGRATION_JSON_SCHEMA).map_err(|e| { - ScratchError::new(StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to serialize JSON schema: {}", e)) - })?; - - Ok(Response::builder() - .header("Content-Type", "application/json") - .body(Body::from(schema_string)) - .unwrap()) -} diff --git a/refact-agent/engine/src/integrations/docker/integr_docker.rs b/refact-agent/engine/src/integrations/docker/integr_docker.rs index 43a995719..bedbe4a5c 100644 --- a/refact-agent/engine/src/integrations/docker/integr_docker.rs +++ b/refact-agent/engine/src/integrations/docker/integr_docker.rs @@ -54,24 +54,9 @@ pub struct ToolDocker { impl IntegrationTrait for ToolDocker { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_docker) => { - tracing::info!("Docker settings applied: {:?}", settings_docker); - self.settings_docker = settings_docker - }, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_docker = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/docker/integr_isolation.rs b/refact-agent/engine/src/integrations/docker/integr_isolation.rs index e59bef1f0..011bac49a 100644 --- a/refact-agent/engine/src/integrations/docker/integr_isolation.rs +++ b/refact-agent/engine/src/integrations/docker/integr_isolation.rs @@ -31,26 +31,11 @@ pub struct IntegrationIsolation { impl IntegrationTrait for IntegrationIsolation { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, _config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_isolation) => { - tracing::info!("Isolation settings applied: {:?}", settings_isolation); - self.settings_isolation = settings_isolation - }, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - Ok(()) - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, _config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_isolation = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; + Ok(()) + } fn integr_settings_as_json(&self) -> Value { serde_json::to_value(&self.settings_isolation).unwrap() diff --git a/refact-agent/engine/src/integrations/integr_abstract.rs b/refact-agent/engine/src/integrations/integr_abstract.rs index 95ab7d0e6..5b94d9bfa 100644 --- a/refact-agent/engine/src/integrations/integr_abstract.rs +++ b/refact-agent/engine/src/integrations/integr_abstract.rs @@ -11,7 +11,7 @@ use crate::global_context::GlobalContext; pub trait IntegrationTrait: Send + Sync { fn as_any(&self) -> &dyn std::any::Any; fn integr_schema(&self) -> &str; - async fn integr_settings_apply(&mut self, gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String>; + async fn integr_settings_apply(&mut self, gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error>; fn integr_settings_as_json(&self) -> serde_json::Value; fn integr_common(&self) -> IntegrationCommon; async fn integr_tools(&self, integr_name: &str) -> Vec>; // integr_name is sometimes different, "cmdline_compile_my_project" != "cmdline" diff --git a/refact-agent/engine/src/integrations/integr_chrome.rs b/refact-agent/engine/src/integrations/integr_chrome.rs index a353f2c3d..532b648a2 100644 --- a/refact-agent/engine/src/integrations/integr_chrome.rs +++ b/refact-agent/engine/src/integrations/integr_chrome.rs @@ -152,21 +152,9 @@ impl IntegrationSession for ChromeSession impl IntegrationTrait for ToolChrome { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_chrome) => self.settings_chrome = settings_chrome, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_chrome = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/integr_cmdline.rs b/refact-agent/engine/src/integrations/integr_cmdline.rs index 9ad5fa7c2..33f28451d 100644 --- a/refact-agent/engine/src/integrations/integr_cmdline.rs +++ b/refact-agent/engine/src/integrations/integr_cmdline.rs @@ -66,21 +66,9 @@ pub struct ToolCmdline { impl IntegrationTrait for ToolCmdline { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(x) => self.cfg = x, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.cfg = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } @@ -148,10 +136,16 @@ fn powershell_escape(s: &str) -> String { pub fn replace_args(x: &str, args_str: &HashMap) -> String { let mut result = x.to_string(); for (key, value) in args_str { - #[cfg(target_os = "windows")] - let escaped_value = powershell_escape(value); - #[cfg(not(target_os = "windows"))] - let escaped_value = escape(Cow::from(value.as_str())).to_string(); + let escaped_value = if value == "" { + // special case for an empty paramter, we want it empty as replacement, rather than escaped empty string "" + "".to_string() + } else { + #[cfg(target_os = "windows")] + let x = powershell_escape(value); + #[cfg(not(target_os = "windows"))] + let x = escape(Cow::from(value.as_str())).to_string(); + x + }; result = result.replace(&format!("%{}%", key), &escaped_value); } result @@ -257,7 +251,7 @@ pub async fn execute_blocking_command( } } -fn parse_command_args(args: &HashMap, cfg: &CmdlineToolConfig) -> Result<(String, String), String> +fn _parse_command_args(args: &HashMap, cfg: &CmdlineToolConfig) -> Result<(String, String), String> { let mut args_str: HashMap = HashMap::new(); let valid_params: Vec = cfg.parameters.iter().map(|p| p.name.clone()).collect(); @@ -293,7 +287,7 @@ impl Tool for ToolCmdline { tool_call_id: &String, args: &HashMap, ) -> Result<(bool, Vec), String> { - let (command, workdir) = parse_command_args(args, &self.cfg)?; + let (command, workdir) = _parse_command_args(args, &self.cfg)?; let gcx = ccx.lock().await.global_context.clone(); let mut error_log = Vec::::new(); @@ -335,7 +329,7 @@ impl Tool for ToolCmdline { &self, args: &HashMap, ) -> Result { - let (command, _workdir) = parse_command_args(args, &self.cfg)?; + let (command, _workdir) = _parse_command_args(args, &self.cfg)?; return Ok(command); } diff --git a/refact-agent/engine/src/integrations/integr_cmdline_service.rs b/refact-agent/engine/src/integrations/integr_cmdline_service.rs index 5892cfcf6..9d2f7c057 100644 --- a/refact-agent/engine/src/integrations/integr_cmdline_service.rs +++ b/refact-agent/engine/src/integrations/integr_cmdline_service.rs @@ -34,21 +34,9 @@ pub struct ToolService { impl IntegrationTrait for ToolService { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(x) => self.cfg = x, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.cfg = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/integr_github.rs b/refact-agent/engine/src/integrations/integr_github.rs index edd707992..01dba31dc 100644 --- a/refact-agent/engine/src/integrations/integr_github.rs +++ b/refact-agent/engine/src/integrations/integr_github.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use std::collections::HashMap; use async_trait::async_trait; -use tracing::{error, info}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex as AMutex; use tokio::sync::RwLock as ARwLock; @@ -35,23 +34,9 @@ pub struct ToolGithub { impl IntegrationTrait for ToolGithub { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_github) => { - self.settings_github = settings_github; - }, - Err(e) => { - error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_github = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } @@ -185,7 +170,7 @@ fn parse_command_args(args: &HashMap) -> Result, Stri return Err("Parsed command is empty".to_string()); } for (i, arg) in parsed_args.iter().enumerate() { - info!("argument[{}]: {}", i, arg); + tracing::info!("argument[{}]: {}", i, arg); } if parsed_args[0] == "gh" { parsed_args.remove(0); diff --git a/refact-agent/engine/src/integrations/integr_gitlab.rs b/refact-agent/engine/src/integrations/integr_gitlab.rs index 7bcce4b94..76d602fd6 100644 --- a/refact-agent/engine/src/integrations/integr_gitlab.rs +++ b/refact-agent/engine/src/integrations/integr_gitlab.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use std::collections::HashMap; use async_trait::async_trait; -use tracing::{error, info}; use tokio::sync::Mutex as AMutex; use tokio::sync::RwLock as ARwLock; use tokio::process::Command; @@ -34,24 +33,9 @@ pub struct ToolGitlab { impl IntegrationTrait for ToolGitlab { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_gitlab) => { - info!("GitLab settings applied: {:?}", settings_gitlab); - self.settings_gitlab = settings_gitlab; - }, - Err(e) => { - error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()) - } - }; - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_gitlab = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } @@ -184,7 +168,7 @@ fn parse_command_args(args: &HashMap) -> Result, Stri return Err("Parsed command is empty".to_string()); } for (i, arg) in parsed_args.iter().enumerate() { - info!("argument[{}]: {}", i, arg); + tracing::info!("argument[{}]: {}", i, arg); } if parsed_args[0] == "glab" { parsed_args.remove(0); diff --git a/refact-agent/engine/src/integrations/integr_mcp.rs b/refact-agent/engine/src/integrations/integr_mcp.rs index 8c98ac572..b41bab26e 100644 --- a/refact-agent/engine/src/integrations/integr_mcp.rs +++ b/refact-agent/engine/src/integrations/integr_mcp.rs @@ -93,7 +93,7 @@ async fn _session_apply_settings( gcx: Arc>, config_path: String, new_cfg: SettingsMCP, -) -> Result<(), String> { +) { let session_key = format!("{}", config_path); let session_arc = { @@ -184,8 +184,6 @@ async fn _session_apply_settings( let session_downcasted = session_locked.as_any_mut().downcast_mut::().unwrap(); session_downcasted.launched_coroutines.push(coroutine); } - - Ok(()) } async fn _session_wait_coroutines( @@ -213,32 +211,12 @@ impl IntegrationTrait for IntegrationMCP { self } - async fn integr_settings_apply( - &mut self, - gcx: Arc>, - config_path: String, - value: &serde_json::Value - ) -> Result<(), String> { + async fn integr_settings_apply(&mut self, gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { self.gcx_option = Some(Arc::downgrade(&gcx)); - - match serde_json::from_value::(value.clone()) { - Ok(x) => self.cfg = x, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - self.config_path = config_path.clone(); - - _session_apply_settings(gcx.clone(), config_path.clone(), self.cfg.clone()).await?; // possibly saves coroutine in session - + self.cfg = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; + self.config_path = config_path; + _session_apply_settings(gcx.clone(), self.config_path.clone(), self.cfg.clone()).await; // possibly saves coroutine in session Ok(()) } diff --git a/refact-agent/engine/src/integrations/integr_mysql.rs b/refact-agent/engine/src/integrations/integr_mysql.rs index 0a141fbea..6b6199be7 100644 --- a/refact-agent/engine/src/integrations/integr_mysql.rs +++ b/refact-agent/engine/src/integrations/integr_mysql.rs @@ -38,21 +38,9 @@ pub struct ToolMysql { impl IntegrationTrait for ToolMysql { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_mysql) => self.settings_mysql = settings_mysql, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_mysql = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/integr_pdb.rs b/refact-agent/engine/src/integrations/integr_pdb.rs index 5e991e949..67acbded9 100644 --- a/refact-agent/engine/src/integrations/integr_pdb.rs +++ b/refact-agent/engine/src/integrations/integr_pdb.rs @@ -73,24 +73,9 @@ impl IntegrationSession for PdbSession impl IntegrationTrait for ToolPdb { fn as_any(&self) -> &dyn Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_pdb) => { - info!("PDB settings applied: {:?}", settings_pdb); - self.settings_pdb = settings_pdb; - }, - Err(e) => { - error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - }; + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_pdb = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/integr_postgres.rs b/refact-agent/engine/src/integrations/integr_postgres.rs index 1f56c30a0..a15ae8e51 100644 --- a/refact-agent/engine/src/integrations/integr_postgres.rs +++ b/refact-agent/engine/src/integrations/integr_postgres.rs @@ -38,24 +38,12 @@ pub struct ToolPostgres { impl IntegrationTrait for ToolPostgres { fn as_any(&self) -> &dyn std::any::Any { self } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(settings_postgres) => self.settings_postgres = settings_postgres, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - self.config_path = config_path; - Ok(()) - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.settings_postgres = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; + self.config_path = config_path; + Ok(()) + } fn integr_settings_as_json(&self) -> Value { serde_json::to_value(&self.settings_postgres).unwrap() diff --git a/refact-agent/engine/src/integrations/integr_shell.rs b/refact-agent/engine/src/integrations/integr_shell.rs index 60c345008..8fc1147a7 100644 --- a/refact-agent/engine/src/integrations/integr_shell.rs +++ b/refact-agent/engine/src/integrations/integr_shell.rs @@ -44,21 +44,9 @@ impl IntegrationTrait for ToolShell { SHELL_INTEGRATION_SCHEMA } - async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), String> { - match serde_json::from_value::(value.clone()) { - Ok(x) => self.cfg = x, - Err(e) => { - tracing::error!("Failed to apply settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } - match serde_json::from_value::(value.clone()) { - Ok(x) => self.common = x, - Err(e) => { - tracing::error!("Failed to apply common settings: {}\n{:?}", e, value); - return Err(e.to_string()); - } - } + async fn integr_settings_apply(&mut self, _gcx: Arc>, config_path: String, value: &serde_json::Value) -> Result<(), serde_json::Error> { + self.cfg = serde_json::from_value(value.clone())?; + self.common = serde_json::from_value(value.clone())?; self.config_path = config_path; Ok(()) } diff --git a/refact-agent/engine/src/integrations/json_schema.rs b/refact-agent/engine/src/integrations/json_schema.rs deleted file mode 100644 index a796e8d05..000000000 --- a/refact-agent/engine/src/integrations/json_schema.rs +++ /dev/null @@ -1,41 +0,0 @@ -use lazy_static::lazy_static; -use serde_json::{json, Value}; - -lazy_static! { -pub static ref INTEGRATION_JSON_SCHEMA: Value = json!({ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "parameters": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]{1,64}$", - "description": "Parameter name - alphanumeric characters, underscores and hyphens, 1-64 chars" - }, - "type": { - "type": "string", - "description": "Parameter type" - }, - "description": { - "type": "string", - "description": "Parameter description" - } - }, - "required": ["name", "type", "description"], - "additionalProperties": false - }, - "description": "List of command parameters" - }, - "name": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]{1,64}$", - "description": "Command name - alphanumeric characters, underscores and hyphens, 1-64 chars" - } - }, - "additionalProperties": true -}); -} diff --git a/refact-agent/engine/src/integrations/mod.rs b/refact-agent/engine/src/integrations/mod.rs index 5ba92371a..ef26536c9 100644 --- a/refact-agent/engine/src/integrations/mod.rs +++ b/refact-agent/engine/src/integrations/mod.rs @@ -29,7 +29,6 @@ pub mod yaml_schema; pub mod setting_up_integrations; pub mod running_integrations; pub mod utils; -pub mod json_schema; use integr_abstract::IntegrationTrait; diff --git a/refact-agent/engine/src/integrations/running_integrations.rs b/refact-agent/engine/src/integrations/running_integrations.rs index 0d35e67aa..1337d345c 100644 --- a/refact-agent/engine/src/integrations/running_integrations.rs +++ b/refact-agent/engine/src/integrations/running_integrations.rs @@ -64,12 +64,12 @@ pub async fn load_integrations( } }; let should_be_fine = integr.integr_settings_apply(gcx.clone(), rec.integr_config_path.clone(), &rec.config_unparsed).await; - if should_be_fine.is_err() { - // tracing::warn!("failed to apply settings for integration {}: {:?}", rec.integr_name, should_be_fine.err()); + if let Err(err) = should_be_fine { + let error_line = err.line(); error_log.push(crate::integrations::setting_up_integrations::YamlError { integr_config_path: rec.integr_config_path.clone(), - error_line: 0, - error_msg: format!("failed to apply settings: {:?}", should_be_fine.err()), + error_line, + error_msg: format!("failed to apply settings: {}", err), }); } integrations_map.insert(rec.integr_name.clone(), integr); diff --git a/refact-agent/engine/src/integrations/setting_up_integrations.rs b/refact-agent/engine/src/integrations/setting_up_integrations.rs index 586a6c261..d3bdcabf3 100644 --- a/refact-agent/engine/src/integrations/setting_up_integrations.rs +++ b/refact-agent/engine/src/integrations/setting_up_integrations.rs @@ -8,9 +8,7 @@ use serde_json::{json, Value}; use tokio::sync::RwLock as ARwLock; use tokio::fs as async_fs; use tokio::io::AsyncWriteExt; -use jsonschema; use crate::global_context::GlobalContext; -use crate::integrations::json_schema::INTEGRATION_JSON_SCHEMA; // use crate::tools::tools_description::Tool; // use crate::yaml_configs::create_configs::{integrations_enabled_cfg, read_yaml_into_value}; @@ -32,16 +30,6 @@ impl From<(&str, &serde_yaml::Error)> for YamlError { } } -impl From<(&str, &jsonschema::ValidationError<'_>)> for YamlError { - fn from((path, err): (&str, &jsonschema::ValidationError)) -> Self { - YamlError { - integr_config_path: path.to_string(), - error_line: 0, // ValidationError doesn't provide line numbers - error_msg: format!("Schema validation error: {}", err), - } - } -} - #[derive(Serialize, Default, Debug, Clone)] pub struct IntegrationRecord { pub project_path: String, @@ -79,11 +67,7 @@ fn get_array_of_str_or_empty(val: &serde_json::Value, path: &str) -> Vec fn parse_and_validate_yaml(path: &str, content: &String) -> Result { let value_yaml = serde_yaml::from_str::(&content) .map_err(|e| YamlError::from((path, &e)))?; - let json_value = serde_json::to_value(value_yaml.clone()).unwrap(); - if let Err(err) = jsonschema::validate(&INTEGRATION_JSON_SCHEMA, &json_value) { - return Err(YamlError::from((path, &err))); - } Ok(json_value) } @@ -103,7 +87,7 @@ pub fn read_integrations_d( // 1. Read and parse integrations.yaml (Optional, used for testing) // This reads the file to be used by (2) and (3), it does not create the records yet. - // --integrations-yaml flag disables global config dir, except for integrations + // --integrations-yaml flag disables global config dir, except for integrations // in `globally_allowed_integrations` list in this yaml file let mut integrations_yaml_value = None; let mut globally_allowed_integration_list = if integrations_yaml_path.is_empty() { @@ -184,7 +168,7 @@ pub fn read_integrations_d( } for (path_str, integr_name, project_path) in files_to_read { - // If --integrations-yaml is set, ignore the global config folder + // If --integrations-yaml is set, ignore the global config folder // except for the list of integrations specified as `globally_allowed_integrations`. if let Some(allowed_integr_list) = &globally_allowed_integration_list { if project_path.is_empty() && !allowed_integr_list.contains(&integr_name) { @@ -518,15 +502,14 @@ pub async fn integration_config_get( Ok(y) => { let j = serde_json::to_value(y).unwrap(); match integration_box.integr_settings_apply(gcx.clone(), better_integr_config_path.clone(), &j).await { - Ok(_) => { - } + Ok(_) => {} Err(err) => { result.error_log.push(YamlError { integr_config_path: better_integr_config_path.clone(), - error_line: 0, + error_line: err.line(), error_msg: err.to_string(), }); - tracing::warn!("cannot deserialize some fields in the integration cfg {better_integr_config_path}: {err}"); + tracing::warn!("cannot deserialize fields in {better_integr_config_path}: {err}"); } } let common_settings = integration_box.integr_common(); @@ -566,7 +549,9 @@ pub async fn integration_config_save( let mut integration_box = crate::integrations::integration_from_name(integr_name.as_str()) .map_err(|e| format!("Failed to load integrations: {}", e))?; - integration_box.integr_settings_apply(gcx.clone(), integr_config_path.clone(), integr_values).await?; // this will produce "no field XXX" errors + integration_box.integr_settings_apply(gcx.clone(), integr_config_path.clone(), integr_values).await + .map_err(|e| format!("validation error at {}:{}: {}", integr_config_path, e.line(), e))?; + let mut sanitized_json: serde_json::Value = integration_box.integr_settings_as_json(); let common_settings = integration_box.integr_common(); if let (Value::Object(sanitized_json_m), Value::Object(common_settings_m)) = (&mut sanitized_json, json!(common_settings)) { diff --git a/refact-agent/engine/src/tools/tools_description.rs b/refact-agent/engine/src/tools/tools_description.rs index 7490344af..f4ea736dc 100644 --- a/refact-agent/engine/src/tools/tools_description.rs +++ b/refact-agent/engine/src/tools/tools_description.rs @@ -292,7 +292,7 @@ tools: parameters_required: - "path" - "content" - + - name: "update_textdoc" agentic: false description: "Updates an existing document by replacing specific text. Optimized for large files or small changes where simple string replacement is sufficient. Prefer this over replace_textdoc for large files." @@ -302,19 +302,19 @@ tools: description: "Absolute path to the file to change." - name: "old_str" type: "string" - description: "The exact text that needs to be updated. Use update_textdoc_regex if you need pattern matching." + description: "The exact text that needs to be updated. Use update_textdoc_regex if you need pattern matching." - name: "replacement" type: "string" - description: "The new text that will replace the old text." + description: "The new text that will replace the old text." - name: "multiple" type: "boolean" - description: "If true, applies the replacement to all occurrences; if false, only the first occurrence is replaced." + description: "If true, applies the replacement to all occurrences; if false, only the first occurrence is replaced." parameters_required: - "path" - "old_str" - "replacement" - "multiple" - + # -- agentic tools below -- - name: "locate" agentic: true @@ -335,7 +335,7 @@ tools: description: "What's the topic and what kind of result do you want?" parameters_required: - "problem_statement" - + - name: "update_textdoc_regex" agentic: true description: "Updates an existing document using regex pattern matching. Ideal when changes can be expressed as a regular expression or when you need to match variable text patterns. May be slower than update_textdoc for large files." @@ -345,19 +345,19 @@ tools: description: "Absolute path to the file to change." - name: "pattern" type: "string" - description: "A regex pattern to match the text that needs to be updated. Prefer simpler regexes for better performance." + description: "A regex pattern to match the text that needs to be updated. Prefer simpler regexes for better performance." - name: "replacement" type: "string" - description: "The new text that will replace the matched pattern." + description: "The new text that will replace the matched pattern." - name: "multiple" type: "boolean" - description: "If true, applies the replacement to all occurrences; if false, only the first occurrence is replaced." + description: "If true, applies the replacement to all occurrences; if false, only the first occurrence is replaced." parameters_required: - "path" - "pattern" - "replacement" - "multiple" - + - name: "replace_textdoc" agentic: true description: "Completely replaces the content of an existing document. Use ONLY for small files, as it rewrites the entire file. For large files or small changes, use update_textdoc instead." @@ -488,12 +488,30 @@ pub struct ToolDesc { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct ToolParam { + #[serde(deserialize_with = "validate_snake_case")] pub name: String, #[serde(rename = "type", default = "default_param_type")] pub param_type: String, pub description: String, } +fn validate_snake_case<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if !s.chars().next().map_or(false, |c| c.is_ascii_lowercase()) + || !s.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_') + || s.contains("__") + || s.ends_with('_') + { + return Err(serde::de::Error::custom( + format!("name {:?} must be in snake_case format: lowercase letters, numbers and single underscores, must start with letter", s) + )); + } + Ok(s) +} + fn default_param_type() -> String { "string".to_string() } diff --git a/refact-agent/engine/src/yaml_configs/customization_compiled_in.yaml b/refact-agent/engine/src/yaml_configs/customization_compiled_in.yaml index 8fb0e9cf2..ff5cecc2e 100644 --- a/refact-agent/engine/src/yaml_configs/customization_compiled_in.yaml +++ b/refact-agent/engine/src/yaml_configs/customization_compiled_in.yaml @@ -25,8 +25,9 @@ CD_INSTRUCTIONS: | SHELL_INSTRUCTIONS: | When running on user's laptop, you most likely have the shell() tool. It's for one-time dependency installations, or doing whatever user is asking you to do. Tools the user can set up are better, because they don't require confimations when running on a laptop. - When doing something typical for the project, offer the user to make a cmdline_* tool after you have run it. - You can do this by writing: + When doing something for the project using shell() tool, offer the user to make a cmdline_* tool after you have successfully run + the shell() call. But double-check that it doesn't already exist, and it is actually typical for this kind of project. You can offer + this by writing: 🧩SETTINGS:cmdline_cargo_check @@ -43,7 +44,7 @@ PROMPT_EXPLORATION_TOOLS: | [mode2] You are Refact Chat, a coding assistant. Core Principles **Determine if the question is related to the current project**: - - **If yes**: + - **If yes**: - Explain your plan briefly before calling any tools - Gather the necessary context using `tree()`, `cat()`, `search()`, `definition()`, `references()` and other tool calls, or follow the user’s instructions. - Ask clarifying questions if needed, making as many iterations as necessary to refine the context. @@ -53,9 +54,9 @@ PROMPT_EXPLORATION_TOOLS: | - Answer the question directly without calling any tools. %CD_INSTRUCTIONS% - + %WORKSPACE_INFO% - + %PROJECT_SUMMARY% @@ -101,35 +102,35 @@ PROMPT_AGENTIC_TOOLS: | PROMPT_THINKING_AGENT: | [mode3] You are Refact Agent, an autonomous bot for coding tasks. - + STRATEGY 1. Gather Maximum Context - - **Objective**: Expand your view of the project so no relevant information is overlooked. - - Use `tree()` to explore the project structure. - - Use `locate()` With the Full Problem Statement + - **Objective**: Expand your view of the project so no relevant information is overlooked. + - Use `tree()` to explore the project structure. + - Use `locate()` With the Full Problem Statement - Use all other tools such as `search()`, `cat()`, `definition()`, etc. to collect every piece of relevant context. - - Open all files that might be indirectly referenced by the code. - 2. Plan Thoroughly With `think()` - - **Objective**: Develop a precise plan before making any changes. - - Provide the full problem statement again in the `problem_statement` argument of `think()`. - - Clearly define the expected output format. - - **Do not** make or apply changes at this point—only plan. + - Open all files that might be indirectly referenced by the code. + 2. Plan Thoroughly With `think()` + - **Objective**: Develop a precise plan before making any changes. + - Provide the full problem statement again in the `problem_statement` argument of `think()`. + - Clearly define the expected output format. + - **Do not** make or apply changes at this point—only plan. - Always gather required context (Step 1) before calling `think()`. - 3. Execute the Plan and Modify the Project - - **Objective**: Implement the step-by-step plan generated by `think()`. - - Make changes incrementally, using tools `*_textdoc()`. + 3. Execute the Plan and Modify the Project + - **Objective**: Implement the step-by-step plan generated by `think()`. + - Make changes incrementally, using tools `*_textdoc()`. - It's a good practice to call cat() to track changes for changed files. - - If any unexpected issues emerge, collect additional context before proceeding. + - If any unexpected issues emerge, collect additional context before proceeding. - Ensure modifications match the original objective and remain consistent across the project. - + ### **IMPORTANT NOTES** - 1. **Parallel Exploration** - - You may use multiple methods in parallel (e.g., searching or opening files) to ensure complete understanding. - 2. **Do Not Modify Files Before `think()`** - - Strictly avoid editing the project until a thorough plan is established in `think()`. - 3. **No Premature `think()`** + 1. **Parallel Exploration** + - You may use multiple methods in parallel (e.g., searching or opening files) to ensure complete understanding. + 2. **Do Not Modify Files Before `think()`** + - Strictly avoid editing the project until a thorough plan is established in `think()`. + 3. **No Premature `think()`** - Only call `think()` after you have gathered the necessary context in Step 2. - + **Comment your plan before each step.** **Comment results of each step.** **Always follow these steps in exact order without skipping or rearranging them.** @@ -169,10 +170,10 @@ PROMPT_CONFIGURATOR: | You can't check if the tool in question works or not in the same thread, user will have to accept the changes, and test again later by starting a new chat. The current config file is %CURRENT_CONFIG% but rewrite variables.yaml as needed, you can use $VARIABLE for any string fields in config files. You can - also use all the variables in secrets.yaml that you can't read or write, but the user can. When writing passwords, always offer this link in a new line: - - 🧩EDITOR:secrets.yaml - + also use all the variables in secrets.yaml that you can't read or write, but the user can. When writing passwords, always offer this link in a new line: + + 🧩EDITOR:secrets.yaml + So the user can open and change it without sending the contents to third parties.