feat(kernel): add internal ZCLAW kernel integration with Tauri

Phase 1-3 of independence architecture:
- zclaw-types: Add ToolDefinition, ToolResult, KernelConfig, ModelConfig
- zclaw-kernel: Fix AgentInfo provider field, export config module
- desktop: Add kernel_commands for internal kernel access
- Add AgentId FromStr implementation for parsing

New Tauri commands:
- kernel_init, kernel_status, kernel_shutdown
- agent_create, agent_list, agent_get, agent_delete
- agent_chat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-22 08:37:20 +08:00
parent 185763868a
commit 7abfca9d5c
6 changed files with 618 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
//! ZCLAW Kernel commands for Tauri
//!
//! These commands provide direct access to the internal ZCLAW Kernel,
//! eliminating the need for external OpenFang process.
use std::sync::Arc;
use tauri::{AppHandle, Manager, State};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use zclaw_kernel::Kernel;
use zclaw_types::{AgentConfig, AgentId, AgentInfo, AgentState};
/// Kernel state wrapper for Tauri
pub type KernelState = Arc<Mutex<Option<Kernel>>>;
/// Agent creation request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateAgentRequest {
/// Agent name
pub name: String,
/// Agent description
#[serde(default)]
pub description: Option<String>,
/// System prompt
#[serde(default)]
pub system_prompt: Option<String>,
/// Model provider
#[serde(default = "default_provider")]
pub provider: String,
/// Model identifier
#[serde(default = "default_model")]
pub model: String,
/// Max tokens
#[serde(default = "default_max_tokens")]
pub max_tokens: u32,
/// Temperature
#[serde(default = "default_temperature")]
pub temperature: f32,
}
fn default_provider() -> String { "anthropic".to_string() }
fn default_model() -> String { "claude-sonnet-4-20250514".to_string() }
fn default_max_tokens() -> u32 { 4096 }
fn default_temperature() -> f32 { 0.7 }
/// Agent creation response
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateAgentResponse {
pub id: String,
pub name: String,
pub state: String,
}
/// Chat request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatRequest {
/// Agent ID
pub agent_id: String,
/// Message content
pub message: String,
}
/// Chat response
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatResponse {
pub content: String,
pub input_tokens: u32,
pub output_tokens: u32,
}
/// Kernel status response
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct KernelStatusResponse {
pub initialized: bool,
pub agent_count: usize,
pub database_url: Option<String>,
pub default_provider: Option<String>,
pub default_model: Option<String>,
}
/// Initialize the internal ZCLAW Kernel
#[tauri::command]
pub async fn kernel_init(
state: State<'_, KernelState>,
) -> Result<KernelStatusResponse, String> {
let mut kernel_lock = state.lock().await;
if kernel_lock.is_some() {
let kernel = kernel_lock.as_ref().unwrap();
return Ok(KernelStatusResponse {
initialized: true,
agent_count: kernel.list_agents().len(),
database_url: None,
default_provider: Some("anthropic".to_string()),
default_model: Some("claude-sonnet-4-20250514".to_string()),
});
}
// Load configuration
let config = zclaw_kernel::config::KernelConfig::default();
// Boot kernel
let kernel = Kernel::boot(config.clone())
.await
.map_err(|e| format!("Failed to initialize kernel: {}", e))?;
let agent_count = kernel.list_agents().len();
*kernel_lock = Some(kernel);
Ok(KernelStatusResponse {
initialized: true,
agent_count,
database_url: Some(config.database_url),
default_provider: Some(config.default_provider),
default_model: Some(config.default_model),
})
}
/// Get kernel status
#[tauri::command]
pub async fn kernel_status(
state: State<'_, KernelState>,
) -> Result<KernelStatusResponse, String> {
let kernel_lock = state.lock().await;
match kernel_lock.as_ref() {
Some(kernel) => Ok(KernelStatusResponse {
initialized: true,
agent_count: kernel.list_agents().len(),
database_url: None,
default_provider: Some("anthropic".to_string()),
default_model: Some("claude-sonnet-4-20250514".to_string()),
}),
None => Ok(KernelStatusResponse {
initialized: false,
agent_count: 0,
database_url: None,
default_provider: None,
default_model: None,
}),
}
}
/// Shutdown the kernel
#[tauri::command]
pub async fn kernel_shutdown(
state: State<'_, KernelState>,
) -> Result<(), String> {
let mut kernel_lock = state.lock().await;
if let Some(kernel) = kernel_lock.take() {
kernel.shutdown().await.map_err(|e| e.to_string())?;
}
Ok(())
}
/// Create a new agent
#[tauri::command]
pub async fn agent_create(
state: State<'_, KernelState>,
request: CreateAgentRequest,
) -> Result<CreateAgentResponse, String> {
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
// Build agent config
let config = AgentConfig::new(&request.name)
.with_description(request.description.unwrap_or_default())
.with_system_prompt(request.system_prompt.unwrap_or_default())
.with_model(zclaw_types::ModelConfig {
provider: request.provider,
model: request.model,
api_key_env: None,
base_url: None,
})
.with_max_tokens(request.max_tokens)
.with_temperature(request.temperature);
let id = kernel.spawn_agent(config)
.await
.map_err(|e| format!("Failed to create agent: {}", e))?;
Ok(CreateAgentResponse {
id: id.to_string(),
name: request.name,
state: "running".to_string(),
})
}
/// List all agents
#[tauri::command]
pub async fn agent_list(
state: State<'_, KernelState>,
) -> Result<Vec<AgentInfo>, String> {
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
Ok(kernel.list_agents())
}
/// Get agent info
#[tauri::command]
pub async fn agent_get(
state: State<'_, KernelState>,
agent_id: String,
) -> Result<Option<AgentInfo>, String> {
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
let id: AgentId = agent_id.parse()
.map_err(|_| "Invalid agent ID format".to_string())?;
Ok(kernel.get_agent(&id))
}
/// Delete an agent
#[tauri::command]
pub async fn agent_delete(
state: State<'_, KernelState>,
agent_id: String,
) -> Result<(), String> {
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
let id: AgentId = agent_id.parse()
.map_err(|_| "Invalid agent ID format".to_string())?;
kernel.kill_agent(&id)
.await
.map_err(|e| format!("Failed to delete agent: {}", e))
}
/// Send a message to an agent
#[tauri::command]
pub async fn agent_chat(
state: State<'_, KernelState>,
request: ChatRequest,
) -> Result<ChatResponse, String> {
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
let id: AgentId = request.agent_id.parse()
.map_err(|_| "Invalid agent ID format".to_string())?;
let response = kernel.send_message(&id, request.message)
.await
.map_err(|e| format!("Chat failed: {}", e))?;
Ok(ChatResponse {
content: response.content,
input_tokens: response.input_tokens,
output_tokens: response.output_tokens,
})
}
/// Create the kernel state for Tauri
pub fn create_kernel_state() -> KernelState {
Arc::new(Mutex::new(None))
}

View File

@@ -24,6 +24,9 @@ mod memory_commands;
// Intelligence Layer (migrated from frontend lib/)
mod intelligence;
// Internal ZCLAW Kernel commands (replaces external OpenFang process)
mod kernel_commands;
use serde::Serialize;
use serde_json::{json, Value};
use std::fs;
@@ -1308,6 +1311,9 @@ pub fn run() {
let reflection_state: intelligence::ReflectionEngineState = std::sync::Arc::new(tokio::sync::Mutex::new(intelligence::ReflectionEngine::new(None)));
let identity_state: intelligence::IdentityManagerState = std::sync::Arc::new(tokio::sync::Mutex::new(intelligence::AgentIdentityManager::new()));
// Initialize internal ZCLAW Kernel state
let kernel_state = kernel_commands::create_kernel_state();
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.manage(browser_state)
@@ -1315,7 +1321,17 @@ pub fn run() {
.manage(heartbeat_state)
.manage(reflection_state)
.manage(identity_state)
.manage(kernel_state)
.invoke_handler(tauri::generate_handler![
// Internal ZCLAW Kernel commands (preferred)
kernel_commands::kernel_init,
kernel_commands::kernel_status,
kernel_commands::kernel_shutdown,
kernel_commands::agent_create,
kernel_commands::agent_list,
kernel_commands::agent_get,
kernel_commands::agent_delete,
kernel_commands::agent_chat,
// OpenFang commands (new naming)
openfang_status,
openfang_start,