fix(audit): 修复深度审计发现的 P0/P1 问题 (8项)
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
基于 DEEP_AUDIT_REPORT.md 修复 2 CRITICAL + 4 HIGH + 1 MEDIUM 问题: - C1: PromptOnly 技能集成 LLM 调用 — 定义 LlmCompleter trait, 通过 LlmDriverAdapter 桥接 zclaw_runtime::LlmDriver, PromptOnlySkill.execute() 现在调用 LLM 生成内容 - C2: 反思引擎空记忆 bug — 新增 query_memories_for_reflection() 从 VikingStorage 查询真实记忆传入 reflect() - H7: Agent Store 接口适配 — KernelClient 添加 listClones/createClone/ deleteClone/updateClone 方法,映射到 agent_* 命令 - H8: Hand 审批检查 — hand_execute 执行前检查 needs_approval, 需审批返回 pending_approval 状态 - M1: 幽灵命令注册 — 注册 hand_get/hand_run_status/hand_run_list 三个 Tauri 桩命令 - H1/H2: SpeechHand/TwitterHand 添加 demo 标签 - H5: 归档过时 VERIFICATION_REPORT 文档更新: DEEP_AUDIT_REPORT.md 标记修复状态,README.md 更新 关键指标和变更历史。整体完成度从 ~50% 提升至 ~58%。
This commit is contained in:
@@ -162,7 +162,7 @@ impl SpeechHand {
|
||||
"rate": { "type": "number" },
|
||||
}
|
||||
})),
|
||||
tags: vec!["audio".to_string(), "tts".to_string(), "education".to_string()],
|
||||
tags: vec!["audio".to_string(), "tts".to_string(), "education".to_string(), "demo".to_string()],
|
||||
enabled: true,
|
||||
},
|
||||
state: Arc::new(RwLock::new(SpeechState {
|
||||
|
||||
@@ -270,7 +270,7 @@ impl TwitterHand {
|
||||
}
|
||||
]
|
||||
})),
|
||||
tags: vec!["twitter".to_string(), "social".to_string(), "automation".to_string()],
|
||||
tags: vec!["twitter".to_string(), "social".to_string(), "automation".to_string(), "demo".to_string()],
|
||||
enabled: true,
|
||||
},
|
||||
credentials: Arc::new(RwLock::new(None)),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Kernel - central coordinator
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc, Mutex};
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||
@@ -13,16 +14,53 @@ use crate::config::KernelConfig;
|
||||
use zclaw_memory::MemoryStore;
|
||||
use zclaw_runtime::{AgentLoop, LlmDriver, ToolRegistry, tool::SkillExecutor};
|
||||
use zclaw_skills::SkillRegistry;
|
||||
use zclaw_skills::LlmCompleter;
|
||||
use zclaw_hands::{HandRegistry, HandContext, HandResult, hands::{BrowserHand, SlideshowHand, SpeechHand, QuizHand, WhiteboardHand, ResearcherHand, CollectorHand, ClipHand, TwitterHand, quiz::LlmQuizGenerator}};
|
||||
|
||||
/// Adapter that bridges `zclaw_runtime::LlmDriver` → `zclaw_skills::LlmCompleter`
|
||||
struct LlmDriverAdapter {
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
}
|
||||
|
||||
impl zclaw_skills::LlmCompleter for LlmDriverAdapter {
|
||||
fn complete(
|
||||
&self,
|
||||
prompt: &str,
|
||||
) -> Pin<Box<dyn std::future::Future<Output = std::result::Result<String, String>> + Send + '_>> {
|
||||
let driver = self.driver.clone();
|
||||
let prompt = prompt.to_string();
|
||||
Box::pin(async move {
|
||||
let request = zclaw_runtime::CompletionRequest {
|
||||
messages: vec![zclaw_types::Message::user(prompt)],
|
||||
max_tokens: Some(4096),
|
||||
temperature: Some(0.7),
|
||||
..Default::default()
|
||||
};
|
||||
let response = driver.complete(request).await
|
||||
.map_err(|e| format!("LLM completion error: {}", e))?;
|
||||
// Extract text from content blocks
|
||||
let text: String = response.content.iter()
|
||||
.filter_map(|block| match block {
|
||||
zclaw_runtime::ContentBlock::Text { text } => Some(text.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
Ok(text)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Skill executor implementation for Kernel
|
||||
pub struct KernelSkillExecutor {
|
||||
skills: Arc<SkillRegistry>,
|
||||
llm: Arc<dyn LlmCompleter>,
|
||||
}
|
||||
|
||||
impl KernelSkillExecutor {
|
||||
pub fn new(skills: Arc<SkillRegistry>) -> Self {
|
||||
Self { skills }
|
||||
pub fn new(skills: Arc<SkillRegistry>, driver: Arc<dyn LlmDriver>) -> Self {
|
||||
let llm: Arc<dyn zclaw_skills::LlmCompleter> = Arc::new(LlmDriverAdapter { driver });
|
||||
Self { skills, llm }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +76,7 @@ impl SkillExecutor for KernelSkillExecutor {
|
||||
let context = zclaw_skills::SkillContext {
|
||||
agent_id: agent_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
llm: Some(self.llm.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
let result = self.skills.execute(&zclaw_types::SkillId::new(skill_id), &context, input).await?;
|
||||
@@ -53,6 +92,7 @@ pub struct Kernel {
|
||||
events: EventBus,
|
||||
memory: Arc<MemoryStore>,
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
llm_completer: Arc<dyn zclaw_skills::LlmCompleter>,
|
||||
skills: Arc<SkillRegistry>,
|
||||
skill_executor: Arc<KernelSkillExecutor>,
|
||||
hands: Arc<HandRegistry>,
|
||||
@@ -99,7 +139,11 @@ impl Kernel {
|
||||
hands.register(Arc::new(TwitterHand::new())).await;
|
||||
|
||||
// Create skill executor
|
||||
let skill_executor = Arc::new(KernelSkillExecutor::new(skills.clone()));
|
||||
let skill_executor = Arc::new(KernelSkillExecutor::new(skills.clone(), driver.clone()));
|
||||
|
||||
// Create LLM completer for skill system (shared with skill_executor)
|
||||
let llm_completer: Arc<dyn zclaw_skills::LlmCompleter> =
|
||||
Arc::new(LlmDriverAdapter { driver: driver.clone() });
|
||||
|
||||
// Initialize trigger manager
|
||||
let trigger_manager = crate::trigger_manager::TriggerManager::new(hands.clone());
|
||||
@@ -117,6 +161,7 @@ impl Kernel {
|
||||
events,
|
||||
memory,
|
||||
driver,
|
||||
llm_completer,
|
||||
skills,
|
||||
skill_executor,
|
||||
hands,
|
||||
@@ -426,7 +471,12 @@ impl Kernel {
|
||||
context: zclaw_skills::SkillContext,
|
||||
input: serde_json::Value,
|
||||
) -> Result<zclaw_skills::SkillResult> {
|
||||
self.skills.execute(&zclaw_types::SkillId::new(id), &context, input).await
|
||||
// Inject LLM completer into context for PromptOnly skills
|
||||
let mut ctx = context;
|
||||
if ctx.llm.is_none() {
|
||||
ctx.llm = Some(self.llm_completer.clone());
|
||||
}
|
||||
self.skills.execute(&zclaw_types::SkillId::new(id), &ctx, input).await
|
||||
}
|
||||
|
||||
/// Get the hands registry
|
||||
|
||||
@@ -4,6 +4,7 @@ use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
use tracing::warn;
|
||||
use zclaw_types::Result;
|
||||
|
||||
use super::{Skill, SkillContext, SkillManifest, SkillResult};
|
||||
@@ -38,8 +39,21 @@ impl Skill for PromptOnlySkill {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
async fn execute(&self, _context: &SkillContext, input: Value) -> Result<SkillResult> {
|
||||
async fn execute(&self, context: &SkillContext, input: Value) -> Result<SkillResult> {
|
||||
let prompt = self.format_prompt(&input);
|
||||
|
||||
// If an LLM completer is available, generate an AI response
|
||||
if let Some(completer) = &context.llm {
|
||||
match completer.complete(&prompt).await {
|
||||
Ok(response) => return Ok(SkillResult::success(Value::String(response))),
|
||||
Err(e) => {
|
||||
warn!("[PromptOnlySkill] LLM completion failed: {}, falling back to raw prompt", e);
|
||||
// Fall through to return raw prompt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No LLM available — return formatted prompt (backward compatible)
|
||||
Ok(SkillResult::success(Value::String(prompt)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,21 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::pin::Pin;
|
||||
use zclaw_types::{SkillId, Result};
|
||||
|
||||
/// Type-erased LLM completion interface.
|
||||
///
|
||||
/// Defined here (in zclaw-skills) to avoid a circular dependency on zclaw-runtime.
|
||||
/// Implementations live in zclaw-kernel where both crates are available.
|
||||
pub trait LlmCompleter: Send + Sync {
|
||||
/// Complete a simple prompt → response (no system prompt, no tools).
|
||||
fn complete(
|
||||
&self,
|
||||
prompt: &str,
|
||||
) -> Pin<Box<dyn std::future::Future<Output = std::result::Result<String, String>> + Send + '_>>;
|
||||
}
|
||||
|
||||
/// Skill manifest definition
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SkillManifest {
|
||||
@@ -63,7 +76,7 @@ pub enum SkillMode {
|
||||
}
|
||||
|
||||
/// Skill execution context
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct SkillContext {
|
||||
/// Agent ID executing the skill
|
||||
pub agent_id: String,
|
||||
@@ -79,6 +92,22 @@ pub struct SkillContext {
|
||||
pub network_allowed: bool,
|
||||
/// Whether to allow file system access
|
||||
pub file_access_allowed: bool,
|
||||
/// Optional LLM completer for skills that need AI generation (e.g. PromptOnly)
|
||||
pub llm: Option<std::sync::Arc<dyn LlmCompleter>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SkillContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SkillContext")
|
||||
.field("agent_id", &self.agent_id)
|
||||
.field("session_id", &self.session_id)
|
||||
.field("working_dir", &self.working_dir)
|
||||
.field("timeout_secs", &self.timeout_secs)
|
||||
.field("network_allowed", &self.network_allowed)
|
||||
.field("file_access_allowed", &self.file_access_allowed)
|
||||
.field("llm", &self.llm.as_ref().map(|_| "Arc<dyn LlmCompleter>"))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SkillContext {
|
||||
@@ -91,6 +120,7 @@ impl Default for SkillContext {
|
||||
timeout_secs: 60,
|
||||
network_allowed: false,
|
||||
file_access_allowed: false,
|
||||
llm: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user