release(v0.2.0): streaming, MCP protocol, Browser Hand, security enhancements
## Major Features ### Streaming Response System - Implement LlmDriver trait with `stream()` method returning async Stream - Add SSE parsing for Anthropic and OpenAI API streaming - Integrate Tauri event system for frontend streaming (`stream:chunk` events) - Add StreamChunk types: Delta, ToolStart, ToolEnd, Complete, Error ### MCP Protocol Implementation - Add MCP JSON-RPC 2.0 types (mcp_types.rs) - Implement stdio-based MCP transport (mcp_transport.rs) - Support tool discovery, execution, and resource operations ### Browser Hand Implementation - Complete browser automation with Playwright-style actions - Support Navigate, Click, Type, Scrape, Screenshot, Wait actions - Add educational Hands: Whiteboard, Slideshow, Speech, Quiz ### Security Enhancements - Implement command whitelist/blacklist for shell_exec tool - Add SSRF protection with private IP blocking - Create security.toml configuration file ## Test Improvements - Fix test import paths (security-utils, setup) - Fix vi.mock hoisting issues with vi.hoisted() - Update test expectations for validateUrl and sanitizeFilename - Add getUnsupportedLocalGatewayStatus mock ## Documentation Updates - Update architecture documentation - Improve configuration reference - Add quick-start guide updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
//! - USER.md auto-update by agent (stores learned preferences)
|
||||
//! - SOUL.md/AGENTS.md change proposals (require user approval)
|
||||
//! - Snapshot history for rollback
|
||||
//! - File system persistence (survives app restart)
|
||||
//!
|
||||
//! Phase 3 of Intelligence Layer Migration.
|
||||
//! Reference: ZCLAW_AGENT_INTELLIGENCE_EVOLUTION.md §6.2.3
|
||||
@@ -12,6 +13,9 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
// === Types ===
|
||||
|
||||
@@ -107,20 +111,107 @@ _尚未收集到用户偏好信息。随着交互积累,此文件将自动更
|
||||
|
||||
// === Agent Identity Manager ===
|
||||
|
||||
pub struct AgentIdentityManager {
|
||||
/// Data structure for disk persistence
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct IdentityStore {
|
||||
identities: HashMap<String, IdentityFiles>,
|
||||
proposals: Vec<IdentityChangeProposal>,
|
||||
snapshots: Vec<IdentitySnapshot>,
|
||||
snapshot_counter: usize,
|
||||
}
|
||||
|
||||
pub struct AgentIdentityManager {
|
||||
identities: HashMap<String, IdentityFiles>,
|
||||
proposals: Vec<IdentityChangeProposal>,
|
||||
snapshots: Vec<IdentitySnapshot>,
|
||||
snapshot_counter: usize,
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl AgentIdentityManager {
|
||||
/// Create a new identity manager with persistence
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let data_dir = Self::get_data_dir();
|
||||
let mut manager = Self {
|
||||
identities: HashMap::new(),
|
||||
proposals: Vec::new(),
|
||||
snapshots: Vec::new(),
|
||||
snapshot_counter: 0,
|
||||
data_dir,
|
||||
};
|
||||
manager.load_from_disk();
|
||||
manager
|
||||
}
|
||||
|
||||
/// Get the data directory for identity storage
|
||||
fn get_data_dir() -> PathBuf {
|
||||
// Use ~/.zclaw/identity/ as the data directory
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
home.join(".zclaw").join("identity")
|
||||
} else {
|
||||
// Fallback to current directory
|
||||
PathBuf::from(".zclaw").join("identity")
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all data from disk
|
||||
fn load_from_disk(&mut self) {
|
||||
let store_path = self.data_dir.join("store.json");
|
||||
if !store_path.exists() {
|
||||
return; // No saved data, use defaults
|
||||
}
|
||||
|
||||
match fs::read_to_string(&store_path) {
|
||||
Ok(content) => {
|
||||
match serde_json::from_str::<IdentityStore>(&content) {
|
||||
Ok(store) => {
|
||||
self.identities = store.identities;
|
||||
self.proposals = store.proposals;
|
||||
self.snapshots = store.snapshots;
|
||||
self.snapshot_counter = store.snapshot_counter;
|
||||
eprintln!(
|
||||
"[IdentityManager] Loaded {} identities, {} proposals, {} snapshots",
|
||||
self.identities.len(),
|
||||
self.proposals.len(),
|
||||
self.snapshots.len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("[IdentityManager] Failed to parse store.json: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("[IdentityManager] Failed to read store.json: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Save all data to disk
|
||||
fn save_to_disk(&self) {
|
||||
// Ensure directory exists
|
||||
if let Err(e) = fs::create_dir_all(&self.data_dir) {
|
||||
error!("[IdentityManager] Failed to create data directory: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let store = IdentityStore {
|
||||
identities: self.identities.clone(),
|
||||
proposals: self.proposals.clone(),
|
||||
snapshots: self.snapshots.clone(),
|
||||
snapshot_counter: self.snapshot_counter,
|
||||
};
|
||||
|
||||
let store_path = self.data_dir.join("store.json");
|
||||
match serde_json::to_string_pretty(&store) {
|
||||
Ok(content) => {
|
||||
if let Err(e) = fs::write(&store_path, content) {
|
||||
error!("[IdentityManager] Failed to write store.json: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[IdentityManager] Failed to serialize data: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +275,7 @@ impl AgentIdentityManager {
|
||||
let mut updated = identity.clone();
|
||||
updated.user_profile = new_content.to_string();
|
||||
self.identities.insert(agent_id.to_string(), updated);
|
||||
self.save_to_disk();
|
||||
}
|
||||
|
||||
/// Append to user profile
|
||||
@@ -219,6 +311,7 @@ impl AgentIdentityManager {
|
||||
};
|
||||
|
||||
self.proposals.push(proposal.clone());
|
||||
self.save_to_disk();
|
||||
proposal
|
||||
}
|
||||
|
||||
@@ -256,6 +349,7 @@ impl AgentIdentityManager {
|
||||
// Update proposal status
|
||||
self.proposals[proposal_idx].status = ProposalStatus::Approved;
|
||||
|
||||
self.save_to_disk();
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
@@ -268,6 +362,7 @@ impl AgentIdentityManager {
|
||||
.ok_or_else(|| "Proposal not found or not pending".to_string())?;
|
||||
|
||||
proposal.status = ProposalStatus::Rejected;
|
||||
self.save_to_disk();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -301,6 +396,7 @@ impl AgentIdentityManager {
|
||||
}
|
||||
|
||||
self.identities.insert(agent_id.to_string(), updated);
|
||||
self.save_to_disk();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -375,6 +471,7 @@ impl AgentIdentityManager {
|
||||
|
||||
self.identities
|
||||
.insert(agent_id.to_string(), files);
|
||||
self.save_to_disk();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -388,6 +485,7 @@ impl AgentIdentityManager {
|
||||
self.identities.remove(agent_id);
|
||||
self.proposals.retain(|p| p.agent_id != agent_id);
|
||||
self.snapshots.retain(|s| s.agent_id != agent_id);
|
||||
self.save_to_disk();
|
||||
}
|
||||
|
||||
/// Export all identities for backup
|
||||
@@ -400,6 +498,7 @@ impl AgentIdentityManager {
|
||||
for (agent_id, files) in identities {
|
||||
self.identities.insert(agent_id, files);
|
||||
}
|
||||
self.save_to_disk();
|
||||
}
|
||||
|
||||
/// Get all proposals (for debugging)
|
||||
|
||||
Reference in New Issue
Block a user