feat: 高德地图工具 + 多轮 bug 修复

新增:
- 高德地图 6 个内置工具: amap_poi_search, amap_geocode, amap_reverse_geocode, amap_route_plan, amap_travel_plan, amap_map_link
- CLI 子命令: ias tool amap (poi-search/geocode/route-plan/travel-plan/map-link)
- 工具优先级指南: build_capability_guide() 含 amap > weather > web_search 优先级
- DEFAULT_SYSTEM_PROMPT 内置工具优先级说明(不再依赖环境变量)

Bug 修复:
1. (HIGH) call_capability 参数契约修复 — 新增 unpack_call_params() 统一解包 prompt 字段
2. (MEDIUM) 天气 hours=0 不再调用逐时 API
3. (MEDIUM) 长期记忆 daemon 双写缓存+DB,无 DB 不丢失
4. (HIGH) 审批流程状态语义修复 — handle_reply 返回 ApprovalDecision,拒绝/过期不再当作通过
5. (HIGH) 审批有效期基于 expires_at 时间戳而非 receiver drop
6. (MEDIUM) 摘要保存改为 current_user_id 与读取一致
7. (MEDIUM) worker 审批通过后删除孤立 tool result,仅保留 approved_tool 状态放行

架构:
- daemon + worker IPC 架构 (Unix Domain Socket)
- build_capability_guide 共享函数消除 main.rs/worker.rs 重复
This commit is contained in:
2026-06-03 17:01:04 +08:00
parent e379e498b5
commit b2fe054eaf
23 changed files with 4367 additions and 263 deletions
+9 -5
View File
@@ -3,6 +3,7 @@ use sha2::{Digest, Sha256};
use sqlx::PgPool;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::{Mutex, oneshot};
/// 审批决策
@@ -18,6 +19,7 @@ struct PendingEntry {
code_hash: String,
skill_name: String,
attempts_left: i32,
expires_at: Instant,
/// 通知等待方
sender: oneshot::Sender<ApprovalDecision>,
}
@@ -80,6 +82,7 @@ impl ApprovalManager {
code_hash,
skill_name: skill_name.to_string(),
attempts_left: 3,
expires_at: Instant::now() + std::time::Duration::from_secs(300),
sender: tx,
});
@@ -87,8 +90,8 @@ impl ApprovalManager {
}
/// 处理用户回复(由消息循环调用)
/// 匹配成功时返回 Some(技能名)匹配失败返回 None
pub async fn handle_reply(&self, user_id: &str, reply: &str) -> Option<String> {
/// 返回 (技能名, 决策)匹配失败返回 None
pub async fn handle_reply(&self, user_id: &str, reply: &str) -> Option<(String, ApprovalDecision)> {
let (result, name, code_hash) = {
let mut map = self.pending.lock().await;
let entries = map.get_mut(user_id)?;
@@ -141,17 +144,18 @@ impl ApprovalManager {
.await;
}
Some(name)
Some((name, result))
}
/// 清理过期审批(通知等待方 + 更新 DB)
/// 清理过期审批(按时间判定,通知等待方 + 更新 DB
pub async fn clean_expired(&self) {
let now = Instant::now();
let mut expired = Vec::new();
{
let map = self.pending.lock().await;
for (uid, entries) in map.iter() {
for (i, e) in entries.iter().enumerate() {
if e.sender.is_closed() {
if now > e.expires_at {
expired.push((uid.clone(), i, e.code_hash.clone()));
}
}