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:
iven
2026-03-24 03:24:24 +08:00
parent e49ba4460b
commit 3ff08faa56
78 changed files with 29575 additions and 1682 deletions

View File

@@ -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)