refactor(kernel,desktop): chat.rs 瘦身 Phase 2 — 548→458行 (-16%)
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
- 提取 translate_event() 函数: LoopEvent→StreamChatEvent 翻译独立 - 提取 Kernel::try_intercept_schedule(): 调度拦截下沉到 kernel - 新增 ScheduleInterceptResult 类型导出 - 所有缝测试 14/14 PASS,无回归 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,109 @@ pub struct ChatModeConfig {
|
|||||||
pub subagent_enabled: Option<bool>,
|
pub subagent_enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Result of a successful schedule intent interception.
|
||||||
|
pub struct ScheduleInterceptResult {
|
||||||
|
/// Pre-built streaming receiver with confirmation message.
|
||||||
|
pub rx: mpsc::Receiver<zclaw_runtime::LoopEvent>,
|
||||||
|
/// Human-readable task description.
|
||||||
|
pub task_description: String,
|
||||||
|
/// Natural language description of the schedule.
|
||||||
|
pub natural_description: String,
|
||||||
|
/// Cron expression.
|
||||||
|
pub cron_expression: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kernel {
|
||||||
|
/// Try to intercept a schedule intent from the user's message.
|
||||||
|
///
|
||||||
|
/// If the message contains a clear schedule intent (e.g., "每天早上9点提醒我查房"),
|
||||||
|
/// parse it, create a trigger, and return a streaming receiver with the
|
||||||
|
/// confirmation message. Returns `Ok(None)` if no interception occurred.
|
||||||
|
pub async fn try_intercept_schedule(
|
||||||
|
&self,
|
||||||
|
message: &str,
|
||||||
|
agent_id: &AgentId,
|
||||||
|
) -> Result<Option<ScheduleInterceptResult>> {
|
||||||
|
if !zclaw_runtime::nl_schedule::has_schedule_intent(message) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parse_result = zclaw_runtime::nl_schedule::parse_nl_schedule(message, agent_id);
|
||||||
|
|
||||||
|
match parse_result {
|
||||||
|
zclaw_runtime::nl_schedule::ScheduleParseResult::Exact(ref parsed)
|
||||||
|
if parsed.confidence >= 0.8 =>
|
||||||
|
{
|
||||||
|
let trigger_id = format!(
|
||||||
|
"sched_{}_{}",
|
||||||
|
chrono::Utc::now().timestamp_millis(),
|
||||||
|
&uuid::Uuid::new_v4().to_string()[..8]
|
||||||
|
);
|
||||||
|
let trigger_config = zclaw_hands::TriggerConfig {
|
||||||
|
id: trigger_id.clone(),
|
||||||
|
name: parsed.task_description.clone(),
|
||||||
|
hand_id: "_reminder".to_string(),
|
||||||
|
trigger_type: zclaw_hands::TriggerType::Schedule {
|
||||||
|
cron: parsed.cron_expression.clone(),
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
max_executions_per_hour: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.create_trigger(trigger_config).await {
|
||||||
|
Ok(_entry) => {
|
||||||
|
tracing::info!(
|
||||||
|
"[Kernel] Schedule trigger created: {} (cron: {})",
|
||||||
|
trigger_id, parsed.cron_expression
|
||||||
|
);
|
||||||
|
let confirm_msg = format!(
|
||||||
|
"已为您设置定时任务:\n\n- **任务**:{}\n- **时间**:{}\n- **Cron**:`{}`\n\n任务已激活,将在设定时间自动执行。",
|
||||||
|
parsed.task_description,
|
||||||
|
parsed.natural_description,
|
||||||
|
parsed.cron_expression,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::channel(32);
|
||||||
|
if tx.send(zclaw_runtime::LoopEvent::Delta(confirm_msg)).await.is_err() {
|
||||||
|
tracing::warn!("[Kernel] Failed to send confirm msg to channel");
|
||||||
|
}
|
||||||
|
if tx.send(zclaw_runtime::LoopEvent::Complete(
|
||||||
|
zclaw_runtime::AgentLoopResult {
|
||||||
|
response: String::new(),
|
||||||
|
input_tokens: 0,
|
||||||
|
output_tokens: 0,
|
||||||
|
iterations: 1,
|
||||||
|
}
|
||||||
|
)).await.is_err() {
|
||||||
|
tracing::warn!("[Kernel] Failed to send complete to channel");
|
||||||
|
}
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
Ok(Some(ScheduleInterceptResult {
|
||||||
|
rx,
|
||||||
|
task_description: parsed.task_description.clone(),
|
||||||
|
natural_description: parsed.natural_description.clone(),
|
||||||
|
cron_expression: parsed.cron_expression.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"[Kernel] Failed to create schedule trigger, falling through to LLM: {}", e
|
||||||
|
);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Kernel] Schedule intent detected but not confident enough, falling through to LLM"
|
||||||
|
);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use zclaw_runtime::{AgentLoop, tool::builtin::PathValidator};
|
use zclaw_runtime::{AgentLoop, tool::builtin::PathValidator};
|
||||||
|
|
||||||
use super::Kernel;
|
use super::Kernel;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use zclaw_hands::{HandRegistry, hands::{BrowserHand, QuizHand, ResearcherHand, C
|
|||||||
pub use adapters::KernelSkillExecutor;
|
pub use adapters::KernelSkillExecutor;
|
||||||
pub use adapters::KernelHandExecutor;
|
pub use adapters::KernelHandExecutor;
|
||||||
pub use messaging::ChatModeConfig;
|
pub use messaging::ChatModeConfig;
|
||||||
|
pub use messaging::ScheduleInterceptResult;
|
||||||
|
|
||||||
/// The ZCLAW Kernel
|
/// The ZCLAW Kernel
|
||||||
pub struct Kernel {
|
pub struct Kernel {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use zclaw_types::AgentId;
|
|||||||
|
|
||||||
use super::{validate_agent_id, KernelState, SessionStreamGuard, StreamCancelFlags};
|
use super::{validate_agent_id, KernelState, SessionStreamGuard, StreamCancelFlags};
|
||||||
use crate::intelligence::validation::validate_string_length;
|
use crate::intelligence::validation::validate_string_length;
|
||||||
|
use zclaw_runtime::LoopEvent;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Request / Response types
|
// Request / Response types
|
||||||
@@ -60,6 +61,47 @@ pub enum StreamChatEvent {
|
|||||||
Error { message: String },
|
Error { message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Translate a runtime LoopEvent into a Tauri StreamChatEvent.
|
||||||
|
///
|
||||||
|
/// Hand tools (name starts with "hand_") are mapped to HandStart/HandEnd
|
||||||
|
/// variants; all other tool events use ToolStart/ToolEnd.
|
||||||
|
fn translate_event(event: &zclaw_runtime::LoopEvent) -> StreamChatEvent {
|
||||||
|
match event {
|
||||||
|
LoopEvent::Delta(delta) => StreamChatEvent::Delta { delta: delta.clone() },
|
||||||
|
LoopEvent::ThinkingDelta(delta) => StreamChatEvent::ThinkingDelta { delta: delta.clone() },
|
||||||
|
LoopEvent::ToolStart { name, input } => {
|
||||||
|
if name.starts_with("hand_") {
|
||||||
|
StreamChatEvent::HandStart { name: name.clone(), params: input.clone() }
|
||||||
|
} else {
|
||||||
|
StreamChatEvent::ToolStart { name: name.clone(), input: input.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoopEvent::ToolEnd { name, output } => {
|
||||||
|
if name.starts_with("hand_") {
|
||||||
|
StreamChatEvent::HandEnd { name: name.clone(), result: output.clone() }
|
||||||
|
} else {
|
||||||
|
StreamChatEvent::ToolEnd { name: name.clone(), output: output.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoopEvent::SubtaskStatus { task_id, description, status, detail } => {
|
||||||
|
StreamChatEvent::SubtaskStatus {
|
||||||
|
task_id: task_id.clone(),
|
||||||
|
description: description.clone(),
|
||||||
|
status: status.clone(),
|
||||||
|
detail: detail.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoopEvent::IterationStart { iteration, max_iterations } => {
|
||||||
|
StreamChatEvent::IterationStart { iteration: *iteration, max_iterations: *max_iterations }
|
||||||
|
}
|
||||||
|
LoopEvent::Complete(result) => StreamChatEvent::Complete {
|
||||||
|
input_tokens: result.input_tokens,
|
||||||
|
output_tokens: result.output_tokens,
|
||||||
|
},
|
||||||
|
LoopEvent::Error(message) => StreamChatEvent::Error { message: message.clone() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Streaming chat request
|
/// Streaming chat request
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -218,152 +260,66 @@ pub async fn agent_chat_stream(
|
|||||||
).await.unwrap_or_default();
|
).await.unwrap_or_default();
|
||||||
|
|
||||||
// --- Schedule intent interception ---
|
// --- Schedule intent interception ---
|
||||||
// If the user's message contains a schedule intent (e.g. "每天早上9点提醒我查房"),
|
// Try to intercept schedule intents (e.g. "每天早上9点提醒我查房") at the kernel level.
|
||||||
// parse it with NlScheduleParser, create a trigger, and return confirmation
|
// If intercepted, returns a pre-built confirmation stream — no LLM call needed.
|
||||||
// directly without calling the LLM.
|
let (mut rx, llm_driver) = {
|
||||||
let mut captured_parsed: Option<zclaw_runtime::nl_schedule::ParsedSchedule> = None;
|
|
||||||
|
|
||||||
if zclaw_runtime::nl_schedule::has_schedule_intent(&message) {
|
|
||||||
let parse_result = zclaw_runtime::nl_schedule::parse_nl_schedule(&message, &id);
|
|
||||||
|
|
||||||
match parse_result {
|
|
||||||
zclaw_runtime::nl_schedule::ScheduleParseResult::Exact(ref parsed)
|
|
||||||
if parsed.confidence >= 0.8 =>
|
|
||||||
{
|
|
||||||
// Try to create a schedule trigger
|
|
||||||
let kernel_lock = state.lock().await;
|
|
||||||
if let Some(kernel) = kernel_lock.as_ref() {
|
|
||||||
// Use UUID fragment to avoid collision under high concurrency
|
|
||||||
let trigger_id = format!(
|
|
||||||
"sched_{}_{}",
|
|
||||||
chrono::Utc::now().timestamp_millis(),
|
|
||||||
&uuid::Uuid::new_v4().to_string()[..8]
|
|
||||||
);
|
|
||||||
let trigger_config = zclaw_hands::TriggerConfig {
|
|
||||||
id: trigger_id.clone(),
|
|
||||||
name: parsed.task_description.clone(),
|
|
||||||
hand_id: "_reminder".to_string(),
|
|
||||||
trigger_type: zclaw_hands::TriggerType::Schedule {
|
|
||||||
cron: parsed.cron_expression.clone(),
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
// 60/hour = once per minute max, reasonable for scheduled tasks
|
|
||||||
max_executions_per_hour: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
match kernel.create_trigger(trigger_config).await {
|
|
||||||
Ok(_entry) => {
|
|
||||||
tracing::info!(
|
|
||||||
"[agent_chat_stream] Schedule trigger created: {} (cron: {})",
|
|
||||||
trigger_id, parsed.cron_expression
|
|
||||||
);
|
|
||||||
captured_parsed = Some(parsed.clone());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!(
|
|
||||||
"[agent_chat_stream] Failed to create schedule trigger, falling through to LLM: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Ambiguous, Unclear, or low confidence — let LLM handle it naturally
|
|
||||||
tracing::debug!(
|
|
||||||
"[agent_chat_stream] Schedule intent detected but not confident enough, falling through to LLM"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the streaming receiver while holding the lock, then release it
|
|
||||||
// NOTE: When schedule_intercepted, llm_driver is None so post_conversation_hook
|
|
||||||
// (memory extraction, heartbeat, reflection) is intentionally skipped —
|
|
||||||
// schedule confirmations are system messages, not user conversations.
|
|
||||||
let (mut rx, llm_driver) = if let Some(parsed) = captured_parsed {
|
|
||||||
// Schedule was intercepted — build confirmation message directly
|
|
||||||
let confirm_msg = format!(
|
|
||||||
"已为您设置定时任务:\n\n- **任务**:{}\n- **时间**:{}\n- **Cron**:`{}`\n\n任务已激活,将在设定时间自动执行。",
|
|
||||||
parsed.task_description,
|
|
||||||
parsed.natural_description,
|
|
||||||
parsed.cron_expression,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
|
||||||
if tx.send(zclaw_runtime::LoopEvent::Delta(confirm_msg)).await.is_err() {
|
|
||||||
tracing::warn!("[agent_chat_stream] Failed to send confirm msg to new channel");
|
|
||||||
}
|
|
||||||
if tx.send(zclaw_runtime::LoopEvent::Complete(
|
|
||||||
zclaw_runtime::AgentLoopResult {
|
|
||||||
response: String::new(),
|
|
||||||
input_tokens: 0,
|
|
||||||
output_tokens: 0,
|
|
||||||
iterations: 1,
|
|
||||||
}
|
|
||||||
)).await.is_err() {
|
|
||||||
tracing::warn!("[agent_chat_stream] Failed to send complete to new channel");
|
|
||||||
}
|
|
||||||
drop(tx);
|
|
||||||
(rx, None)
|
|
||||||
} else {
|
|
||||||
// Normal LLM chat path
|
|
||||||
let kernel_lock = state.lock().await;
|
let kernel_lock = state.lock().await;
|
||||||
let kernel = kernel_lock.as_ref()
|
let kernel = kernel_lock.as_ref()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
|
||||||
// Cleanup on error: release guard + cancel flag
|
|
||||||
err_cleanup_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
err_cleanup_guard.remove(&err_cleanup_session_id);
|
|
||||||
err_cleanup_cancel.remove(&err_cleanup_session_id);
|
|
||||||
"Kernel not initialized. Call kernel_init first.".to_string()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let driver = Some(kernel.driver());
|
match kernel.try_intercept_schedule(&message, &id).await {
|
||||||
|
Ok(Some(intercept)) => {
|
||||||
let prompt_arg = if enhanced_prompt.is_empty() { None } else { Some(enhanced_prompt) };
|
tracing::info!("[agent_chat_stream] Schedule intercepted: {}", intercept.task_description);
|
||||||
|
(intercept.rx, None)
|
||||||
let session_id_parsed = if session_id.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
match uuid::Uuid::parse_str(&session_id) {
|
|
||||||
Ok(uuid) => Some(zclaw_types::SessionId::from_uuid(uuid)),
|
|
||||||
Err(e) => {
|
|
||||||
// Cleanup on error
|
|
||||||
err_cleanup_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
err_cleanup_guard.remove(&err_cleanup_session_id);
|
|
||||||
err_cleanup_cancel.remove(&err_cleanup_session_id);
|
|
||||||
return Err(format!(
|
|
||||||
"Invalid session_id '{}': {}. Cannot reuse conversation context.",
|
|
||||||
session_id, e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
_ => {
|
||||||
// Build chat mode config from request parameters
|
// No interception or error — normal LLM chat path
|
||||||
let chat_mode_config = zclaw_kernel::ChatModeConfig {
|
let driver = Some(kernel.driver());
|
||||||
thinking_enabled: request.thinking_enabled,
|
|
||||||
reasoning_effort: request.reasoning_effort.clone(),
|
|
||||||
plan_mode: request.plan_mode,
|
|
||||||
subagent_enabled: request.subagent_enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
let rx = kernel.send_message_stream_with_prompt(
|
let prompt_arg = if enhanced_prompt.is_empty() { None } else { Some(enhanced_prompt) };
|
||||||
&id,
|
|
||||||
message.clone(),
|
let session_id_parsed = if session_id.is_empty() {
|
||||||
prompt_arg,
|
None
|
||||||
session_id_parsed,
|
} else {
|
||||||
Some(chat_mode_config),
|
match uuid::Uuid::parse_str(&session_id) {
|
||||||
request.model.clone(),
|
Ok(uuid) => Some(zclaw_types::SessionId::from_uuid(uuid)),
|
||||||
)
|
Err(e) => {
|
||||||
.await
|
err_cleanup_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
.map_err(|e| {
|
err_cleanup_guard.remove(&err_cleanup_session_id);
|
||||||
// Cleanup on error
|
err_cleanup_cancel.remove(&err_cleanup_session_id);
|
||||||
err_cleanup_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
return Err(format!(
|
||||||
err_cleanup_guard.remove(&err_cleanup_session_id);
|
"Invalid session_id '{}': {}. Cannot reuse conversation context.",
|
||||||
err_cleanup_cancel.remove(&err_cleanup_session_id);
|
session_id, e
|
||||||
format!("Failed to start streaming: {}", e)
|
));
|
||||||
})?;
|
}
|
||||||
(rx, driver)
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let chat_mode_config = zclaw_kernel::ChatModeConfig {
|
||||||
|
thinking_enabled: request.thinking_enabled,
|
||||||
|
reasoning_effort: request.reasoning_effort.clone(),
|
||||||
|
plan_mode: request.plan_mode,
|
||||||
|
subagent_enabled: request.subagent_enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
let rx = kernel.send_message_stream_with_prompt(
|
||||||
|
&id,
|
||||||
|
message.clone(),
|
||||||
|
prompt_arg,
|
||||||
|
session_id_parsed,
|
||||||
|
Some(chat_mode_config),
|
||||||
|
request.model.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
err_cleanup_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
err_cleanup_guard.remove(&err_cleanup_session_id);
|
||||||
|
err_cleanup_cancel.remove(&err_cleanup_session_id);
|
||||||
|
format!("Failed to start streaming: {}", e)
|
||||||
|
})?;
|
||||||
|
(rx, driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let hb_state = heartbeat_state.inner().clone();
|
let hb_state = heartbeat_state.inner().clone();
|
||||||
@@ -415,69 +371,23 @@ pub async fn agent_chat_stream(
|
|||||||
|
|
||||||
match tokio::time::timeout(stream_timeout, rx.recv()).await {
|
match tokio::time::timeout(stream_timeout, rx.recv()).await {
|
||||||
Ok(Some(event)) => {
|
Ok(Some(event)) => {
|
||||||
let stream_event = match &event {
|
// Fire post-conversation hooks before translating (memory extraction, heartbeat, reflection)
|
||||||
LoopEvent::Delta(delta) => {
|
if let LoopEvent::Complete(result) = &event {
|
||||||
tracing::trace!("[agent_chat_stream] Delta: {} bytes", delta.len());
|
tracing::info!("[agent_chat_stream] Complete: input_tokens={}, output_tokens={}",
|
||||||
StreamChatEvent::Delta { delta: delta.clone() }
|
result.input_tokens, result.output_tokens);
|
||||||
}
|
let agent_id_hook = agent_id_str.clone();
|
||||||
LoopEvent::ThinkingDelta(delta) => {
|
let message_hook = message.clone();
|
||||||
tracing::trace!("[agent_chat_stream] ThinkingDelta: {} bytes", delta.len());
|
let hb = hb_state.clone();
|
||||||
StreamChatEvent::ThinkingDelta { delta: delta.clone() }
|
let rf = rf_state.clone();
|
||||||
}
|
let driver = llm_driver.clone();
|
||||||
LoopEvent::ToolStart { name, input } => {
|
tokio::spawn(async move {
|
||||||
tracing::debug!("[agent_chat_stream] ToolStart: {}", name);
|
crate::intelligence_hooks::post_conversation_hook(
|
||||||
if name.starts_with("hand_") {
|
&agent_id_hook, &message_hook, &hb, &rf, driver,
|
||||||
StreamChatEvent::HandStart { name: name.clone(), params: input.clone() }
|
).await;
|
||||||
} else {
|
});
|
||||||
StreamChatEvent::ToolStart { name: name.clone(), input: input.clone() }
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopEvent::ToolEnd { name, output } => {
|
|
||||||
tracing::debug!("[agent_chat_stream] ToolEnd: {}", name);
|
|
||||||
if name.starts_with("hand_") {
|
|
||||||
StreamChatEvent::HandEnd { name: name.clone(), result: output.clone() }
|
|
||||||
} else {
|
|
||||||
StreamChatEvent::ToolEnd { name: name.clone(), output: output.clone() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopEvent::SubtaskStatus { task_id, description, status, detail } => {
|
|
||||||
tracing::debug!("[agent_chat_stream] SubtaskStatus: {} - {} (id={})", description, status, task_id);
|
|
||||||
StreamChatEvent::SubtaskStatus {
|
|
||||||
task_id: task_id.clone(),
|
|
||||||
description: description.clone(),
|
|
||||||
status: status.clone(),
|
|
||||||
detail: detail.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopEvent::IterationStart { iteration, max_iterations } => {
|
|
||||||
tracing::debug!("[agent_chat_stream] IterationStart: {}/{}", iteration, max_iterations);
|
|
||||||
StreamChatEvent::IterationStart { iteration: *iteration, max_iterations: *max_iterations }
|
|
||||||
}
|
|
||||||
LoopEvent::Complete(result) => {
|
|
||||||
tracing::info!("[agent_chat_stream] Complete: input_tokens={}, output_tokens={}",
|
|
||||||
result.input_tokens, result.output_tokens);
|
|
||||||
|
|
||||||
let agent_id_hook = agent_id_str.clone();
|
let stream_event = translate_event(&event);
|
||||||
let message_hook = message.clone();
|
|
||||||
let hb = hb_state.clone();
|
|
||||||
let rf = rf_state.clone();
|
|
||||||
let driver = llm_driver.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
crate::intelligence_hooks::post_conversation_hook(
|
|
||||||
&agent_id_hook, &message_hook, &hb, &rf, driver,
|
|
||||||
).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
StreamChatEvent::Complete {
|
|
||||||
input_tokens: result.input_tokens,
|
|
||||||
output_tokens: result.output_tokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoopEvent::Error(message) => {
|
|
||||||
tracing::warn!("[agent_chat_stream] Error: {}", message);
|
|
||||||
StreamChatEvent::Error { message: message.clone() }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = app.emit("stream:chunk", serde_json::json!({
|
if let Err(e) = app.emit("stream:chunk", serde_json::json!({
|
||||||
"sessionId": session_id,
|
"sessionId": session_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user