fix(growth,kernel,runtime,desktop): 50 轮功能链路审计 7 项断链修复
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
P0 修复: - B-MEM-2: 跨会话记忆丢失 — 添加 IdentityRecall 查询意图检测, 身份类查询绕过 FTS5/LIKE 文本搜索,直接按 scope 检索全部偏好+知识记忆; 缓存 GrowthIntegration 到 Kernel 避免每次请求重建空 scorer - B-HAND-1: Hands 未触发 — 创建 HandTool wrapper 实现 Tool trait, 在 create_tool_registry() 中注册所有已启用 Hands 为 LLM 可调用工具 P1 修复: - B-SCHED-4: 一次性定时未拦截 — 添加 RE_ONE_SHOT_TODAY 正则匹配 "下午3点半提醒我..."类无日期前缀的同日触发模式 - B-CHAT-2: 工具调用循环 — ToolErrorMiddleware 添加连续失败计数器, 3 次连续失败后自动 AbortLoop 防止无限重试 - B-CHAT-5: Stream 竞态 — cancelStream 后添加 500ms cancelCooldown, 防止后端 active-stream 检查竞态
This commit is contained in:
@@ -129,6 +129,15 @@ static RE_ONE_SHOT: LazyLock<Regex> = LazyLock::new(|| {
|
||||
)).expect("static regex pattern is valid")
|
||||
});
|
||||
|
||||
/// Matches same-day one-shot triggers: "下午3点半提醒我..." or "上午10点提醒我..."
|
||||
/// Pattern: period + time + "提醒我" (no date prefix — implied today)
|
||||
static RE_ONE_SHOT_TODAY: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(&format!(
|
||||
r"^{}(\d{{1,2}})[点时::](?:(\d{{1,2}})|(半))?.*提醒我",
|
||||
PERIOD
|
||||
)).expect("static regex pattern is valid")
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper lookups (pure functions, no allocation)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -395,38 +404,70 @@ fn try_monthly(input: &str, task_desc: &str, agent_id: &AgentId) -> Option<Sched
|
||||
}
|
||||
|
||||
fn try_one_shot(input: &str, task_desc: &str, agent_id: &AgentId) -> Option<ScheduleParseResult> {
|
||||
let caps = RE_ONE_SHOT.captures(input)?;
|
||||
let day_offset = match caps.get(1)?.as_str() {
|
||||
"明天" => 1,
|
||||
"后天" => 2,
|
||||
"大后天" => 3,
|
||||
_ => return None,
|
||||
};
|
||||
let period = caps.get(2).map(|m| m.as_str());
|
||||
let raw_hour: u32 = caps.get(3)?.as_str().parse().ok()?;
|
||||
let minute: u32 = extract_minute(&caps, 4, 5);
|
||||
let hour = adjust_hour_for_period(raw_hour, period);
|
||||
if hour > 23 || minute > 59 {
|
||||
return None;
|
||||
// First try explicit date prefix: 明天/后天/大后天 + time
|
||||
if let Some(caps) = RE_ONE_SHOT.captures(input) {
|
||||
let day_offset = match caps.get(1)?.as_str() {
|
||||
"明天" => 1,
|
||||
"后天" => 2,
|
||||
"大后天" => 3,
|
||||
_ => return None,
|
||||
};
|
||||
let period = caps.get(2).map(|m| m.as_str());
|
||||
let raw_hour: u32 = caps.get(3)?.as_str().parse().ok()?;
|
||||
let minute: u32 = extract_minute(&caps, 4, 5);
|
||||
let hour = adjust_hour_for_period(raw_hour, period);
|
||||
if hour > 23 || minute > 59 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = chrono::Utc::now()
|
||||
.checked_add_signed(chrono::Duration::days(day_offset))
|
||||
.unwrap_or_else(chrono::Utc::now)
|
||||
.with_hour(hour)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_minute(minute)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_second(0)
|
||||
.unwrap_or_else(|| chrono::Utc::now());
|
||||
|
||||
return Some(ScheduleParseResult::Exact(ParsedSchedule {
|
||||
cron_expression: target.to_rfc3339(),
|
||||
natural_description: format!("{} {:02}:{:02}", caps.get(1)?.as_str(), hour, minute),
|
||||
confidence: 0.88,
|
||||
task_description: task_desc.to_string(),
|
||||
task_target: TaskTarget::Agent(agent_id.to_string()),
|
||||
}));
|
||||
}
|
||||
|
||||
let target = chrono::Utc::now()
|
||||
.checked_add_signed(chrono::Duration::days(day_offset))
|
||||
.unwrap_or_else(chrono::Utc::now)
|
||||
.with_hour(hour)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_minute(minute)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_second(0)
|
||||
.unwrap_or_else(|| chrono::Utc::now());
|
||||
// Then try same-day implicit: "下午3点半提醒我..." (no date prefix)
|
||||
if let Some(caps) = RE_ONE_SHOT_TODAY.captures(input) {
|
||||
let period = caps.get(1).map(|m| m.as_str());
|
||||
let raw_hour: u32 = caps.get(2)?.as_str().parse().ok()?;
|
||||
let minute: u32 = extract_minute(&caps, 3, 4);
|
||||
let hour = adjust_hour_for_period(raw_hour, period);
|
||||
if hour > 23 || minute > 59 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ScheduleParseResult::Exact(ParsedSchedule {
|
||||
cron_expression: target.to_rfc3339(),
|
||||
natural_description: format!("{} {:02}:{:02}", caps.get(1)?.as_str(), hour, minute),
|
||||
confidence: 0.88,
|
||||
task_description: task_desc.to_string(),
|
||||
task_target: TaskTarget::Agent(agent_id.to_string()),
|
||||
}))
|
||||
let target = chrono::Utc::now()
|
||||
.with_hour(hour)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_minute(minute)
|
||||
.unwrap_or_else(|| chrono::Utc::now())
|
||||
.with_second(0)
|
||||
.unwrap_or_else(|| chrono::Utc::now());
|
||||
|
||||
let period_desc = period.unwrap_or("");
|
||||
return Some(ScheduleParseResult::Exact(ParsedSchedule {
|
||||
cron_expression: target.to_rfc3339(),
|
||||
natural_description: format!("今天{} {:02}:{:02}", period_desc, hour, minute),
|
||||
confidence: 0.82,
|
||||
task_description: task_desc.to_string(),
|
||||
task_target: TaskTarget::Agent(agent_id.to_string()),
|
||||
}));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user