diff --git a/README.md b/README.md index 8ca836f..ba59985 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Multi-layer security checks prevent: ## Agents Team Configuration System -Cowork Forge V3 introduces a **data-driven configuration system** that transforms hardcoded Agent, Stage, Flow, and Skill definitions into configurable JSON formats, enabling unprecedented flexibility without code modifications. +Cowork Forge introduces a **data-driven configuration system** that transforms hardcoded Agent, Stage, Flow, and Skill definitions into configurable JSON formats, enabling unprecedented flexibility without code modifications. ### Custom Workflows (Flow) diff --git a/crates/cowork-core/src/config_definition/builtin.rs b/crates/cowork-core/src/config_definition/builtin.rs index eeaf2f5..7640139 100644 --- a/crates/cowork-core/src/config_definition/builtin.rs +++ b/crates/cowork-core/src/config_definition/builtin.rs @@ -7,7 +7,7 @@ use anyhow::Result; use include_dir::{include_dir, Dir}; use super::registry::ConfigRegistry; -use super::loader::LoadReport; +use super::registry::LoadReport; // Embed the default configurations directory static DEFAULT_CONFIGS: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/config_definition/default_configs"); diff --git a/crates/cowork-core/src/config_definition/loader.rs b/crates/cowork-core/src/config_definition/loader.rs deleted file mode 100644 index afce1f0..0000000 --- a/crates/cowork-core/src/config_definition/loader.rs +++ /dev/null @@ -1,293 +0,0 @@ -// Configuration Loader - Load configurations from file system -// -// Supports loading from: -// - System default directory: .cowork-v3/config/ -// - User directory: ~/.cowork/config/ -// - Project directory: project/.cowork-v3/config/ -// -// Note: Skills are managed separately via adk-skill (SKILL.md format) -// See the `skills` module for skill management. - -use std::path::{Path, PathBuf}; -use std::fs; -use anyhow::{Result, Context}; -use walkdir::WalkDir; - -use super::agent_definition::AgentDefinition; -use super::stage_definition::StageDefinition; -use super::flow_definition::FlowDefinition; -use super::integration_definition::IntegrationDefinition; -use super::registry::ConfigRegistry; - -/// Configuration loader for file system based config loading -pub struct ConfigLoader { - /// Base directories to search for configurations (in priority order) - search_paths: Vec, -} - -impl ConfigLoader { - /// Create a new loader with default search paths - pub fn new(project_path: Option<&Path>) -> Self { - let mut search_paths = Vec::new(); - - // Project-level config (highest priority) - if let Some(project) = project_path { - search_paths.push(project.join(".cowork-v3").join("config")); - } - - // User-level config - if let Some(home) = dirs::home_dir() { - search_paths.push(home.join(".cowork").join("config")); - } - - // System-level config (built-in defaults) - if let Some(exe_dir) = std::env::current_exe().ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - { - search_paths.push(exe_dir.join("config")); - } - - Self { search_paths } - } - - /// Create a loader with custom search paths - pub fn with_search_paths(search_paths: Vec) -> Self { - Self { search_paths } - } - - /// Get the search paths - pub fn search_paths(&self) -> &[PathBuf] { - &self.search_paths - } - - /// Load all configurations into the registry - pub fn load_all(&self, registry: &ConfigRegistry) -> Result { - let mut report = LoadReport::default(); - - // Load in order: agents, stages, flows, integrations - // Note: Skills are managed via adk-skill module - for path in &self.search_paths { - if !path.exists() { - continue; - } - - self.load_agents(path, registry, &mut report)?; - self.load_stages(path, registry, &mut report)?; - self.load_flows(path, registry, &mut report)?; - self.load_integrations(path, registry, &mut report)?; - } - - // Set default flow if not set - if registry.get_default_flow().is_none() { - if let Some(flow) = registry.get_flow("default") { - registry.set_default_flow(Some(flow.id))?; - report.default_flow_set = true; - } - } - - Ok(report) - } - - /// Load agent definitions from a directory - fn load_agents(&self, base: &Path, registry: &ConfigRegistry, report: &mut LoadReport) -> Result<()> { - let agents_dir = base.join("agents"); - if !agents_dir.exists() { - return Ok(()); - } - - for entry in WalkDir::new(&agents_dir) - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_name().to_string_lossy().ends_with(".json")) - { - let path = entry.path(); - match self.load_agent_file(path) { - Ok(agent) => { - let id = agent.id.clone(); - registry.register_agent(agent)?; - report.agents_loaded += 1; - tracing::debug!("Loaded agent: {} from {:?}", id, path); - } - Err(e) => { - report.errors.push(format!("Failed to load agent from {:?}: {}", path, e)); - tracing::warn!("Failed to load agent from {:?}: {}", path, e); - } - } - } - - Ok(()) - } - - /// Load a single agent file - fn load_agent_file(&self, path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read file: {:?}", path))?; - - let agent: AgentDefinition = serde_json::from_str(&content) - .with_context(|| format!("Failed to parse agent definition: {:?}", path))?; - - Ok(agent) - } - - /// Load stage definitions from a directory - fn load_stages(&self, base: &Path, registry: &ConfigRegistry, report: &mut LoadReport) -> Result<()> { - let stages_dir = base.join("stages"); - if !stages_dir.exists() { - return Ok(()); - } - - for entry in WalkDir::new(&stages_dir) - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_name().to_string_lossy().ends_with(".json")) - { - let path = entry.path(); - match self.load_stage_file(path) { - Ok(stage) => { - let id = stage.id.clone(); - registry.register_stage(stage)?; - report.stages_loaded += 1; - tracing::debug!("Loaded stage: {} from {:?}", id, path); - } - Err(e) => { - report.errors.push(format!("Failed to load stage from {:?}: {}", path, e)); - tracing::warn!("Failed to load stage from {:?}: {}", path, e); - } - } - } - - Ok(()) - } - - /// Load a single stage file - fn load_stage_file(&self, path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read file: {:?}", path))?; - - let stage: StageDefinition = serde_json::from_str(&content) - .with_context(|| format!("Failed to parse stage definition: {:?}", path))?; - - Ok(stage) - } - - /// Load flow definitions from a directory - fn load_flows(&self, base: &Path, registry: &ConfigRegistry, report: &mut LoadReport) -> Result<()> { - let flows_dir = base.join("flows"); - if !flows_dir.exists() { - return Ok(()); - } - - for entry in WalkDir::new(&flows_dir) - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_name().to_string_lossy().ends_with(".json")) - { - let path = entry.path(); - match self.load_flow_file(path) { - Ok(flow) => { - let id = flow.id.clone(); - registry.register_flow(flow)?; - report.flows_loaded += 1; - tracing::debug!("Loaded flow: {} from {:?}", id, path); - } - Err(e) => { - report.errors.push(format!("Failed to load flow from {:?}: {}", path, e)); - tracing::warn!("Failed to load flow from {:?}: {}", path, e); - } - } - } - - Ok(()) - } - - /// Load a single flow file - fn load_flow_file(&self, path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read file: {:?}", path))?; - - let flow: FlowDefinition = serde_json::from_str(&content) - .with_context(|| format!("Failed to parse flow definition: {:?}", path))?; - - Ok(flow) - } - - /// Load integration definitions from a directory - fn load_integrations(&self, base: &Path, registry: &ConfigRegistry, report: &mut LoadReport) -> Result<()> { - let integrations_dir = base.join("integrations"); - if !integrations_dir.exists() { - return Ok(()); - } - - for entry in WalkDir::new(&integrations_dir) - .follow_links(true) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_name().to_string_lossy().ends_with(".json")) - { - let path = entry.path(); - match self.load_integration_file(path) { - Ok(integration) => { - let id = integration.id.clone(); - registry.register_integration(integration)?; - report.integrations_loaded += 1; - tracing::debug!("Loaded integration: {} from {:?}", id, path); - } - Err(e) => { - report.errors.push(format!("Failed to load integration from {:?}: {}", path, e)); - tracing::warn!("Failed to load integration from {:?}: {}", path, e); - } - } - } - - Ok(()) - } - - /// Load a single integration file - fn load_integration_file(&self, path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read file: {:?}", path))?; - - let integration: IntegrationDefinition = serde_json::from_str(&content) - .with_context(|| format!("Failed to parse integration definition: {:?}", path))?; - - Ok(integration) - } -} - -/// Report of loading results -#[derive(Debug, Clone, Default)] -pub struct LoadReport { - pub agents_loaded: usize, - pub stages_loaded: usize, - pub flows_loaded: usize, - pub integrations_loaded: usize, - pub default_flow_set: bool, - pub errors: Vec, -} - -impl LoadReport { - pub fn total_loaded(&self) -> usize { - self.agents_loaded + self.stages_loaded + self.flows_loaded + self.integrations_loaded - } - - pub fn has_errors(&self) -> bool { - !self.errors.is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::TempDir; - - #[test] - fn test_loader_search_paths() { - let temp = TempDir::new().unwrap(); - let loader = ConfigLoader::new(Some(temp.path())); - - assert!(!loader.search_paths().is_empty()); - } -} \ No newline at end of file diff --git a/crates/cowork-core/src/config_definition/mod.rs b/crates/cowork-core/src/config_definition/mod.rs index af3f833..e6a9528 100644 --- a/crates/cowork-core/src/config_definition/mod.rs +++ b/crates/cowork-core/src/config_definition/mod.rs @@ -1,5 +1,5 @@ -// Configuration Definition Module for V3 -// +// Configuration Definition Module +// // This module provides data-driven configuration for Agents, Stages, Flows, // and Integrations. It enables the transition from hardcoded definitions // to configurable, extensible system architecture. @@ -11,7 +11,6 @@ pub mod stage_definition; pub mod flow_definition; pub mod integration_definition; pub mod registry; -pub mod loader; pub mod validator; pub mod builtin; pub mod agent_factory; @@ -21,7 +20,6 @@ pub use stage_definition::*; pub use flow_definition::*; pub use integration_definition::*; pub use registry::*; -pub use loader::*; pub use validator::*; pub use builtin::load_builtin_configs; pub use agent_factory::{create_agent_for_stage, create_agent_from_config, initialize_config_registry}; diff --git a/crates/cowork-core/src/config_definition/registry.rs b/crates/cowork-core/src/config_definition/registry.rs index 9630e1a..b87fe97 100644 --- a/crates/cowork-core/src/config_definition/registry.rs +++ b/crates/cowork-core/src/config_definition/registry.rs @@ -630,6 +630,27 @@ pub struct LoadUserReport { pub errors: Vec, } +/// Report of loading configurations (used by builtin loader) +#[derive(Debug, Clone, Default)] +pub struct LoadReport { + pub agents_loaded: usize, + pub stages_loaded: usize, + pub flows_loaded: usize, + pub integrations_loaded: usize, + pub default_flow_set: bool, + pub errors: Vec, +} + +impl LoadReport { + pub fn total_loaded(&self) -> usize { + self.agents_loaded + self.stages_loaded + self.flows_loaded + self.integrations_loaded + } + + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } +} + /// Settings that persist across sessions #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { diff --git a/crates/cowork-core/src/lib.rs b/crates/cowork-core/src/lib.rs index 0111d0a..e803e9f 100644 --- a/crates/cowork-core/src/lib.rs +++ b/crates/cowork-core/src/lib.rs @@ -3,7 +3,7 @@ // Global configuration pub mod config; -// V3 Configuration Definition Layer +// Configuration Definition Layer pub mod config_definition; // ACP (Agent Client Protocol) for external coding agent integration @@ -75,7 +75,7 @@ pub use config_definition::{ StageDefinition, StageType, HookConfig, HookPoint, ArtifactConfig, StageRetryConfig, FlowDefinition, StageReference, FlowConfig, MemoryScope, InheritanceConfig, InheritanceMode, IntegrationDefinition, IntegrationType, ConnectionConfig, AuthConfig, IntegrationEvent, - ConfigRegistry, global_registry, ConfigLoader, LoadReport, ConfigValidator, ValidationResult, + ConfigRegistry, global_registry, LoadReport, ConfigValidator, ValidationResult, create_agent_for_stage, create_agent_from_config, initialize_config_registry, }; @@ -110,4 +110,4 @@ pub use importer::{ }; // Version info -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); \ No newline at end of file +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/crates/cowork-gui/src/types/config.ts b/crates/cowork-gui/src/types/config.ts index f5e9ff5..a311b42 100644 --- a/crates/cowork-gui/src/types/config.ts +++ b/crates/cowork-gui/src/types/config.ts @@ -1,10 +1,9 @@ /** - * V3 Configuration Types * Matches the Rust backend config_definition types */ // Agent Types -export type AgentType = 'simple' | { loop: { max_iterations?: number } }; +export type AgentType = "simple" | { loop: { max_iterations?: number } }; export interface ModelConfig { model_id?: string; @@ -18,7 +17,7 @@ export interface ToolReference { config?: Record; } -export type IncludeContentsMode = 'none' | 'all' | { selected: string[] }; +export type IncludeContentsMode = "none" | "all" | { selected: string[] }; export interface AgentDefinition { id: string; @@ -36,9 +35,21 @@ export interface AgentDefinition { } // Stage Types -export type StageType = 'idea' | 'prd' | 'design' | 'plan' | 'coding' | 'check' | 'delivery'; - -export type HookPoint = 'pre_execute' | 'post_execute' | 'pre_confirmation' | 'post_confirmation' | 'on_failure'; +export type StageType = + | "idea" + | "prd" + | "design" + | "plan" + | "coding" + | "check" + | "delivery"; + +export type HookPoint = + | "pre_execute" + | "post_execute" + | "pre_confirmation" + | "post_confirmation" + | "on_failure"; export interface HookConfig { integration_id: string; @@ -47,7 +58,7 @@ export interface HookConfig { params?: Record; blocking?: boolean; timeout_secs?: number; - on_failure?: 'ignore' | 'warn' | 'abort'; + on_failure?: "ignore" | "warn" | "abort"; } export interface ArtifactConfig { @@ -78,9 +89,9 @@ export interface StageDefinition { } // Flow Types -export type MemoryScope = 'project' | 'iteration' | 'merged'; +export type MemoryScope = "project" | "iteration" | "merged"; -export type InheritanceMode = 'none' | 'partial' | 'full'; +export type InheritanceMode = "none" | "partial" | "full"; export interface InheritanceConfig { default_mode: InheritanceMode; @@ -143,11 +154,20 @@ export interface SkillInfo { } // Integration Types -export type IntegrationType = 'rest_api' | 'webhook' | 'message_queue' | 'database'; +export type IntegrationType = + | "rest_api" + | "webhook" + | "message_queue" + | "database"; -export type AuthType = 'none' | 'api_key' | 'bearer_token' | 'basic_auth' | 'oauth2'; +export type AuthType = + | "none" + | "api_key" + | "bearer_token" + | "basic_auth" + | "oauth2"; -export type CredentialSource = 'env' | 'config' | 'prompt'; +export type CredentialSource = "env" | "config" | "prompt"; export interface AuthConfig { auth_type: AuthType; @@ -163,7 +183,12 @@ export interface ConnectionConfig { retry_delay_ms?: number; } -export type IntegrationEvent = 'on_stage_start' | 'on_stage_complete' | 'on_flow_start' | 'on_flow_complete' | 'on_error'; +export type IntegrationEvent = + | "on_stage_start" + | "on_stage_complete" + | "on_flow_start" + | "on_flow_complete" + | "on_error"; export interface IntegrationDefinition { id: string; @@ -181,7 +206,7 @@ export interface IntegrationDefinition { export interface ValidationIssue { path: string; message: string; - severity: 'error' | 'warning'; + severity: "error" | "warning"; } export interface ValidationResult { @@ -208,4 +233,4 @@ export interface BuiltinInstruction { } // Instruction type for form -export type InstructionType = 'builtin' | 'file' | 'inline'; +export type InstructionType = "builtin" | "file" | "inline"; diff --git a/litho.docs/en/2.Architecture.md b/litho.docs/en/2.Architecture.md index ba2c683..8711177 100644 --- a/litho.docs/en/2.Architecture.md +++ b/litho.docs/en/2.Architecture.md @@ -918,7 +918,7 @@ flowchart TB ## Configuration System Architecture -Cowork Forge V3 introduces a data-driven configuration system that transforms previously hardcoded Agent, Stage, Flow, Skill, and Integration definitions into configurable JSON formats. This makes the system more flexible and extensible, allowing users to customize development workflows without modifying code. +Cowork Forge introduces a data-driven configuration system that transforms previously hardcoded Agent, Stage, Flow, Skill, and Integration definitions into configurable JSON formats. This makes the system more flexible and extensible, allowing users to customize development workflows without modifying code. ### Configuration Registry