refactor: 统一项目名称从OpenFang到ZCLAW
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
This commit is contained in:
@@ -20,6 +20,7 @@ tokio-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
||||
@@ -252,10 +252,78 @@ fn default_skills_dir() -> Option<std::path::PathBuf> {
|
||||
}
|
||||
|
||||
impl KernelConfig {
|
||||
/// Load configuration from file
|
||||
/// Load configuration from file.
|
||||
///
|
||||
/// Search order:
|
||||
/// 1. Path from `ZCLAW_CONFIG` environment variable
|
||||
/// 2. `~/.zclaw/config.toml`
|
||||
/// 3. Fallback to `Self::default()`
|
||||
///
|
||||
/// Supports `${VAR_NAME}` environment variable interpolation in string values.
|
||||
pub async fn load() -> Result<Self> {
|
||||
// TODO: Load from ~/.zclaw/config.toml
|
||||
Ok(Self::default())
|
||||
let config_path = Self::find_config_path();
|
||||
|
||||
match config_path {
|
||||
Some(path) => {
|
||||
if !path.exists() {
|
||||
tracing::debug!(target: "kernel_config", "Config file not found: {:?}, using defaults", path);
|
||||
return Ok(Self::default());
|
||||
}
|
||||
|
||||
tracing::info!(target: "kernel_config", "Loading config from: {:?}", path);
|
||||
let content = std::fs::read_to_string(&path).map_err(|e| {
|
||||
zclaw_types::ZclawError::Internal(format!("Failed to read config {}: {}", path.display(), e))
|
||||
})?;
|
||||
|
||||
let interpolated = interpolate_env_vars(&content);
|
||||
let mut config: KernelConfig = toml::from_str(&interpolated).map_err(|e| {
|
||||
zclaw_types::ZclawError::Internal(format!("Failed to parse config {}: {}", path.display(), e))
|
||||
})?;
|
||||
|
||||
// Resolve skills_dir if not explicitly set
|
||||
if config.skills_dir.is_none() {
|
||||
config.skills_dir = default_skills_dir();
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: "kernel_config",
|
||||
model = %config.llm.model,
|
||||
base_url = %config.llm.base_url,
|
||||
has_api_key = !config.llm.api_key.is_empty(),
|
||||
"Config loaded successfully"
|
||||
);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
None => Ok(Self::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the config file path.
|
||||
fn find_config_path() -> Option<PathBuf> {
|
||||
// 1. Environment variable override
|
||||
if let Ok(path) = std::env::var("ZCLAW_CONFIG") {
|
||||
return Some(PathBuf::from(path));
|
||||
}
|
||||
|
||||
// 2. ~/.zclaw/config.toml
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
let path = home.join(".zclaw").join("config.toml");
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Project root config/config.toml (for development)
|
||||
let project_config = std::env::current_dir()
|
||||
.ok()
|
||||
.map(|cwd| cwd.join("config").join("config.toml"))?;
|
||||
|
||||
if project_config.exists() {
|
||||
return Some(project_config);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Create the LLM driver
|
||||
@@ -439,3 +507,81 @@ impl LlmConfig {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// === Environment variable interpolation ===
|
||||
|
||||
/// Replace `${VAR_NAME}` patterns in a string with environment variable values.
|
||||
/// If the variable is not set, the pattern is left as-is.
|
||||
fn interpolate_env_vars(content: &str) -> String {
|
||||
let mut result = String::with_capacity(content.len());
|
||||
let mut chars = content.char_indices().peekable();
|
||||
|
||||
while let Some((_, ch)) = chars.next() {
|
||||
if ch == '$' && chars.peek().map(|(_, c)| *c == '{').unwrap_or(false) {
|
||||
chars.next(); // consume '{'
|
||||
|
||||
let mut var_name = String::new();
|
||||
|
||||
while let Some((_, c)) = chars.peek() {
|
||||
match c {
|
||||
'}' => {
|
||||
chars.next(); // consume '}'
|
||||
if let Ok(value) = std::env::var(&var_name) {
|
||||
result.push_str(&value);
|
||||
} else {
|
||||
result.push_str("${");
|
||||
result.push_str(&var_name);
|
||||
result.push('}');
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
var_name.push(*c);
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle unclosed ${... at end of string
|
||||
if !content[result.len()..].contains('}') && var_name.is_empty() {
|
||||
// Already consumed, nothing to do
|
||||
}
|
||||
} else {
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_interpolate_env_vars_basic() {
|
||||
std::env::set_var("ZCLAW_TEST_VAR", "hello");
|
||||
let result = interpolate_env_vars("prefix ${ZCLAW_TEST_VAR} suffix");
|
||||
assert_eq!(result, "prefix hello suffix");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpolate_env_vars_missing() {
|
||||
let result = interpolate_env_vars("${ZCLAW_NONEXISTENT_VAR_12345}");
|
||||
assert_eq!(result, "${ZCLAW_NONEXISTENT_VAR_12345}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpolate_env_vars_no_vars() {
|
||||
let result = interpolate_env_vars("no variables here");
|
||||
assert_eq!(result, "no variables here");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpolate_env_vars_multiple() {
|
||||
std::env::set_var("ZCLAW_TEST_A", "alpha");
|
||||
std::env::set_var("ZCLAW_TEST_B", "beta");
|
||||
let result = interpolate_env_vars("${ZCLAW_TEST_A}-${ZCLAW_TEST_B}");
|
||||
assert_eq!(result, "alpha-beta");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Kernel - central coordinator
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tokio::sync::{broadcast, mpsc, Mutex};
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
@@ -13,7 +13,7 @@ use crate::config::KernelConfig;
|
||||
use zclaw_memory::MemoryStore;
|
||||
use zclaw_runtime::{AgentLoop, LlmDriver, ToolRegistry, tool::SkillExecutor};
|
||||
use zclaw_skills::SkillRegistry;
|
||||
use zclaw_hands::{HandRegistry, HandContext, HandResult, hands::{BrowserHand, SlideshowHand, SpeechHand, QuizHand, WhiteboardHand, ResearcherHand, CollectorHand, ClipHand, TwitterHand}};
|
||||
use zclaw_hands::{HandRegistry, HandContext, HandResult, hands::{BrowserHand, SlideshowHand, SpeechHand, QuizHand, WhiteboardHand, ResearcherHand, CollectorHand, ClipHand, TwitterHand, quiz::LlmQuizGenerator}};
|
||||
|
||||
/// Skill executor implementation for Kernel
|
||||
pub struct KernelSkillExecutor {
|
||||
@@ -57,6 +57,7 @@ pub struct Kernel {
|
||||
skill_executor: Arc<KernelSkillExecutor>,
|
||||
hands: Arc<HandRegistry>,
|
||||
trigger_manager: crate::trigger_manager::TriggerManager,
|
||||
pending_approvals: Arc<Mutex<Vec<ApprovalEntry>>>,
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
@@ -85,10 +86,12 @@ impl Kernel {
|
||||
|
||||
// Initialize hand registry with built-in hands
|
||||
let hands = Arc::new(HandRegistry::new());
|
||||
let quiz_model = config.model().to_string();
|
||||
let quiz_generator = Arc::new(LlmQuizGenerator::new(driver.clone(), quiz_model));
|
||||
hands.register(Arc::new(BrowserHand::new())).await;
|
||||
hands.register(Arc::new(SlideshowHand::new())).await;
|
||||
hands.register(Arc::new(SpeechHand::new())).await;
|
||||
hands.register(Arc::new(QuizHand::new())).await;
|
||||
hands.register(Arc::new(QuizHand::with_generator(quiz_generator))).await;
|
||||
hands.register(Arc::new(WhiteboardHand::new())).await;
|
||||
hands.register(Arc::new(ResearcherHand::new())).await;
|
||||
hands.register(Arc::new(CollectorHand::new())).await;
|
||||
@@ -118,6 +121,7 @@ impl Kernel {
|
||||
skill_executor,
|
||||
hands,
|
||||
trigger_manager,
|
||||
pending_approvals: Arc::new(Mutex::new(Vec::new())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -306,7 +310,8 @@ impl Kernel {
|
||||
.with_model(&model)
|
||||
.with_skill_executor(self.skill_executor.clone())
|
||||
.with_max_tokens(agent_config.max_tokens.unwrap_or_else(|| self.config.max_tokens()))
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()));
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()))
|
||||
.with_compaction_threshold(15_000); // Compact when context exceeds ~15k tokens
|
||||
|
||||
// Build system prompt with skill information injected
|
||||
let system_prompt = self.build_system_prompt_with_skills(agent_config.system_prompt.as_ref()).await;
|
||||
@@ -327,6 +332,16 @@ impl Kernel {
|
||||
&self,
|
||||
agent_id: &AgentId,
|
||||
message: String,
|
||||
) -> Result<mpsc::Receiver<zclaw_runtime::LoopEvent>> {
|
||||
self.send_message_stream_with_prompt(agent_id, message, None).await
|
||||
}
|
||||
|
||||
/// Send a message with streaming and optional external system prompt
|
||||
pub async fn send_message_stream_with_prompt(
|
||||
&self,
|
||||
agent_id: &AgentId,
|
||||
message: String,
|
||||
system_prompt_override: Option<String>,
|
||||
) -> Result<mpsc::Receiver<zclaw_runtime::LoopEvent>> {
|
||||
let agent_config = self.registry.get(agent_id)
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Agent not found: {}", agent_id)))?;
|
||||
@@ -349,10 +364,14 @@ impl Kernel {
|
||||
.with_model(&model)
|
||||
.with_skill_executor(self.skill_executor.clone())
|
||||
.with_max_tokens(agent_config.max_tokens.unwrap_or_else(|| self.config.max_tokens()))
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()));
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()))
|
||||
.with_compaction_threshold(15_000); // Compact when context exceeds ~15k tokens
|
||||
|
||||
// Build system prompt with skill information injected
|
||||
let system_prompt = self.build_system_prompt_with_skills(agent_config.system_prompt.as_ref()).await;
|
||||
// Use external prompt if provided, otherwise build default
|
||||
let system_prompt = match system_prompt_override {
|
||||
Some(prompt) => prompt,
|
||||
None => self.build_system_prompt_with_skills(agent_config.system_prompt.as_ref()).await,
|
||||
};
|
||||
let loop_runner = loop_runner.with_system_prompt(&system_prompt);
|
||||
|
||||
// Run with streaming
|
||||
@@ -477,24 +496,82 @@ impl Kernel {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Approval Management (Stub Implementation)
|
||||
// Approval Management
|
||||
// ============================================================
|
||||
|
||||
/// List pending approvals
|
||||
pub async fn list_approvals(&self) -> Vec<ApprovalEntry> {
|
||||
// Stub: Return empty list
|
||||
Vec::new()
|
||||
let approvals = self.pending_approvals.lock().await;
|
||||
approvals.iter().filter(|a| a.status == "pending").cloned().collect()
|
||||
}
|
||||
|
||||
/// Create a pending approval (called when a needs_approval hand is triggered)
|
||||
pub async fn create_approval(&self, hand_id: String, input: serde_json::Value) -> ApprovalEntry {
|
||||
let entry = ApprovalEntry {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
hand_id,
|
||||
status: "pending".to_string(),
|
||||
created_at: chrono::Utc::now(),
|
||||
input,
|
||||
};
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
approvals.push(entry.clone());
|
||||
entry
|
||||
}
|
||||
|
||||
/// Respond to an approval
|
||||
pub async fn respond_to_approval(
|
||||
&self,
|
||||
_id: &str,
|
||||
_approved: bool,
|
||||
id: &str,
|
||||
approved: bool,
|
||||
_reason: Option<String>,
|
||||
) -> Result<()> {
|
||||
// Stub: Return error
|
||||
Err(zclaw_types::ZclawError::NotFound(format!("Approval not found")))
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending")
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?;
|
||||
|
||||
entry.status = if approved { "approved".to_string() } else { "rejected".to_string() };
|
||||
|
||||
if approved {
|
||||
let hand_id = entry.hand_id.clone();
|
||||
let input = entry.input.clone();
|
||||
drop(approvals); // Release lock before async hand execution
|
||||
|
||||
// Execute the hand in background
|
||||
let hands = self.hands.clone();
|
||||
let approvals = self.pending_approvals.clone();
|
||||
let id_owned = id.to_string();
|
||||
tokio::spawn(async move {
|
||||
let context = HandContext::default();
|
||||
let result = hands.execute(&hand_id, &context, input).await;
|
||||
|
||||
// Update approval status based on execution result
|
||||
let mut approvals = approvals.lock().await;
|
||||
if let Some(entry) = approvals.iter_mut().find(|a| a.id == id_owned) {
|
||||
match result {
|
||||
Ok(_) => entry.status = "completed".to_string(),
|
||||
Err(e) => {
|
||||
entry.status = "failed".to_string();
|
||||
// Store error in input metadata
|
||||
if let Some(obj) = entry.input.as_object_mut() {
|
||||
obj.insert("error".to_string(), Value::String(format!("{}", e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel a pending approval
|
||||
pub async fn cancel_approval(&self, id: &str) -> Result<()> {
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending")
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?;
|
||||
entry.status = "cancelled".to_string();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user