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:
@@ -11,6 +11,9 @@ pub struct MemoryStore {
|
||||
impl MemoryStore {
|
||||
/// Create a new memory store with the given database path
|
||||
pub async fn new(database_url: &str) -> Result<Self> {
|
||||
// Ensure parent directory exists for file-based SQLite databases
|
||||
Self::ensure_database_dir(database_url)?;
|
||||
|
||||
let pool = SqlitePool::connect(database_url).await
|
||||
.map_err(|e| ZclawError::StorageError(e.to_string()))?;
|
||||
let store = Self { pool };
|
||||
@@ -18,6 +21,37 @@ impl MemoryStore {
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// Ensure the parent directory for the database file exists
|
||||
fn ensure_database_dir(database_url: &str) -> Result<()> {
|
||||
// Parse SQLite URL to extract file path
|
||||
// Format: sqlite:/path/to/db or sqlite://path/to/db
|
||||
if database_url.starts_with("sqlite:") {
|
||||
let path_part = database_url.strip_prefix("sqlite:").unwrap();
|
||||
|
||||
// Skip in-memory databases
|
||||
if path_part == ":memory:" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Remove query parameters (e.g., ?mode=rwc)
|
||||
let path_without_query = path_part.split('?').next().unwrap();
|
||||
|
||||
// Handle both absolute and relative paths
|
||||
let path = std::path::Path::new(path_without_query);
|
||||
|
||||
// Get parent directory
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.exists() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|e| ZclawError::StorageError(
|
||||
format!("Failed to create database directory {}: {}", parent.display(), e)
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create an in-memory database (for testing)
|
||||
pub async fn in_memory() -> Result<Self> {
|
||||
Self::new("sqlite::memory:").await
|
||||
@@ -141,7 +175,7 @@ impl MemoryStore {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO messages (session_id, seq, content, created_at)
|
||||
SELECT ?, COALESCE(MAX(seq), 0) + 1, datetime('now')
|
||||
SELECT ?, COALESCE(MAX(seq), 0) + 1, ?, datetime('now')
|
||||
FROM messages WHERE session_id = ?
|
||||
"#,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user