fix(chat): 聊天定时功能断链接通 — NlScheduleParser + _reminder Hand
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

接通"写了没接"的定时功能断链:
- NlScheduleParser has_schedule_intent/parse_nl_schedule 接入 agent_chat_stream
- 新增 _reminder 系统 Hand 作为定时触发器桥接
- TriggerManager hand_id 验证对 _ 前缀系统 Hand 放行
- 聊天消息含定时意图时自动拦截,创建触发器并返回确认消息

验证:cargo check 0 error, 49 tests passed,
Tauri MCP "每天早上9点提醒我查房" → cron 0 9 * * * 确认正确显示
This commit is contained in:
iven
2026-04-15 09:45:19 +08:00
parent 9715f542b6
commit 28c892fd31
7 changed files with 206 additions and 5 deletions

View File

@@ -217,8 +217,90 @@ pub async fn agent_chat_stream(
&identity_state,
).await.unwrap_or_default();
// --- Schedule intent interception ---
// If the user's message contains a schedule intent (e.g. "每天早上9点提醒我查房"),
// parse it with NlScheduleParser, create a trigger, and return confirmation
// directly without calling the LLM.
let mut schedule_intercepted = false;
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() {
let trigger_id = format!("sched_{}", chrono::Utc::now().timestamp_millis());
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 kernel.create_trigger(trigger_config).await {
Ok(_entry) => {
tracing::info!(
"[agent_chat_stream] Schedule trigger created: {} (cron: {})",
trigger_id, parsed.cron_expression
);
schedule_intercepted = true;
}
Err(e) => {
tracing::warn!(
"[agent_chat_stream] Failed to create schedule trigger: {}",
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
let (mut rx, llm_driver) = {
let (mut rx, llm_driver) = if schedule_intercepted {
// Schedule was intercepted — build confirmation message directly
let confirm_msg = {
let parsed = match zclaw_runtime::nl_schedule::parse_nl_schedule(&message, &id) {
zclaw_runtime::nl_schedule::ScheduleParseResult::Exact(p) => p,
_ => unreachable!("schedule_intercepted is only true for Exact results"),
};
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);
let _ = tx.send(zclaw_runtime::LoopEvent::Delta(confirm_msg)).await;
let _ = tx.send(zclaw_runtime::LoopEvent::Complete(
zclaw_runtime::AgentLoopResult {
response: String::new(),
input_tokens: 0,
output_tokens: 0,
iterations: 1,
}
)).await;
drop(tx);
(rx, None)
} else {
// Normal LLM chat path
let kernel_lock = state.lock().await;
let kernel = kernel_lock.as_ref()
.ok_or_else(|| {