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:
+30
-34
@@ -74,10 +74,7 @@ impl WeChatClient {
|
||||
qrcode: &str,
|
||||
base_url: &str,
|
||||
) -> Result<StatusResponse, String> {
|
||||
let url = format!(
|
||||
"{}/ilink/bot/get_qrcode_status?qrcode={}",
|
||||
base_url, qrcode
|
||||
);
|
||||
let url = format!("{}/ilink/bot/get_qrcode_status?qrcode={}", base_url, qrcode);
|
||||
|
||||
let resp_result = self
|
||||
.http
|
||||
@@ -89,7 +86,9 @@ impl WeChatClient {
|
||||
|
||||
match resp_result {
|
||||
Ok(response) => {
|
||||
let text = response.text().await
|
||||
let text = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| format!("读取响应体失败: {}", e))?;
|
||||
serde_json::from_str::<StatusResponse>(&text)
|
||||
.map_err(|e| format!("解析状态响应失败: {}", e))
|
||||
@@ -134,7 +133,8 @@ impl WeChatClient {
|
||||
|
||||
// 生成二维码终端显示
|
||||
if let Ok(code) = qrcode::QrCode::new(qrcode_img_content.as_bytes()) {
|
||||
let svg = code.render::<qrcode::render::unicode::Dense1x2>()
|
||||
let svg = code
|
||||
.render::<qrcode::render::unicode::Dense1x2>()
|
||||
.dark_color(qrcode::render::unicode::Dense1x2::Dark)
|
||||
.light_color(qrcode::render::unicode::Dense1x2::Light)
|
||||
.build();
|
||||
@@ -177,7 +177,9 @@ impl WeChatClient {
|
||||
.ilink_bot_id
|
||||
.ok_or_else(|| "登录成功但未返回 ilink_bot_id".to_string())?;
|
||||
let user_id = status.ilink_user_id.unwrap_or_default();
|
||||
let base_url = status.baseurl.unwrap_or_else(|| Self::DEFAULT_BASE_URL.to_string());
|
||||
let base_url = status
|
||||
.baseurl
|
||||
.unwrap_or_else(|| Self::DEFAULT_BASE_URL.to_string());
|
||||
|
||||
// 保存到内存状态
|
||||
*self.token.lock().await = token.clone();
|
||||
@@ -220,7 +222,10 @@ impl WeChatClient {
|
||||
.map_err(|e| format!("解析 notifyStart 响应失败: {}", e))?;
|
||||
|
||||
if _resp.ret != 0 {
|
||||
return Err(format!("notifyStart 失败: ret={} errmsg={:?}", _resp.ret, _resp.errmsg));
|
||||
return Err(format!(
|
||||
"notifyStart 失败: ret={} errmsg={:?}",
|
||||
_resp.ret, _resp.errmsg
|
||||
));
|
||||
}
|
||||
|
||||
info!("监听器已注册");
|
||||
@@ -257,15 +262,19 @@ impl WeChatClient {
|
||||
let url = format!("{}/ilink/bot/getupdates", self.base_url);
|
||||
let token = self.token.lock().await.clone();
|
||||
|
||||
let resp: GetUpdatesResp = match self
|
||||
.post_json(&url, &body, Some(&token))
|
||||
.await
|
||||
{
|
||||
let resp: GetUpdatesResp = match self.post_json(&url, &body, Some(&token)).await {
|
||||
Ok(resp) => {
|
||||
let resp: GetUpdatesResp = resp.json().await.map_err(|e| format!("解析 getUpdates 响应失败: {}", e))?;
|
||||
let resp: GetUpdatesResp = resp
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("解析 getUpdates 响应失败: {}", e))?;
|
||||
if resp.ret != 0 || resp.errcode.unwrap_or(0) != 0 {
|
||||
tracing::warn!("getUpdates 返回错误: ret={} errcode={:?} errmsg={:?}",
|
||||
resp.ret, resp.errcode, resp.errmsg);
|
||||
tracing::warn!(
|
||||
"getUpdates 返回错误: ret={} errcode={:?} errmsg={:?}",
|
||||
resp.ret,
|
||||
resp.errcode,
|
||||
resp.errmsg
|
||||
);
|
||||
}
|
||||
resp
|
||||
}
|
||||
@@ -337,16 +346,6 @@ impl WeChatClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前 token(用于持久化)
|
||||
pub async fn get_token(&self) -> String {
|
||||
self.token.lock().await.clone()
|
||||
}
|
||||
|
||||
/// 获取当前 account_id(用于持久化)
|
||||
pub async fn get_account_id(&self) -> String {
|
||||
self.account_id.lock().await.clone()
|
||||
}
|
||||
|
||||
/// 获取当前 updates_buf(用于持久化)
|
||||
pub async fn get_updates_buf(&self) -> String {
|
||||
self.updates_buf.lock().await.clone()
|
||||
@@ -375,14 +374,12 @@ impl WeChatClient {
|
||||
|
||||
headers.insert(
|
||||
"iLink-App-ClientVersion",
|
||||
reqwest::header::HeaderValue::from_str(&Self::CLIENT_VERSION.to_string())
|
||||
.unwrap(),
|
||||
reqwest::header::HeaderValue::from_str(&Self::CLIENT_VERSION.to_string()).unwrap(),
|
||||
);
|
||||
|
||||
// X-WECHAT-UIN: random uint32 -> decimal -> base64
|
||||
let uin = rand::random::<u32>();
|
||||
let uin_b64 = base64::engine::general_purpose::STANDARD
|
||||
.encode(uin.to_string());
|
||||
let uin_b64 = base64::engine::general_purpose::STANDARD.encode(uin.to_string());
|
||||
headers.insert(
|
||||
"X-WECHAT-UIN",
|
||||
reqwest::header::HeaderValue::from_str(&uin_b64).unwrap(),
|
||||
@@ -396,8 +393,7 @@ impl WeChatClient {
|
||||
if !token.is_empty() {
|
||||
headers.insert(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", token))
|
||||
.unwrap(),
|
||||
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
"AuthorizationType",
|
||||
@@ -413,7 +409,8 @@ impl WeChatClient {
|
||||
body: &impl serde::Serialize,
|
||||
token: Option<&str>,
|
||||
) -> Result<reqwest::Response, String> {
|
||||
let body_str = serde_json::to_string(body).map_err(|e| format!("序列化请求体失败: {}", e))?;
|
||||
let body_str =
|
||||
serde_json::to_string(body).map_err(|e| format!("序列化请求体失败: {}", e))?;
|
||||
|
||||
debug!("POST {} body_len={}", url, body_str.len());
|
||||
|
||||
@@ -426,8 +423,7 @@ impl WeChatClient {
|
||||
// 设置 Content-Length
|
||||
headers.insert(
|
||||
"Content-Length",
|
||||
reqwest::header::HeaderValue::from_str(&body_str.len().to_string())
|
||||
.unwrap(),
|
||||
reqwest::header::HeaderValue::from_str(&body_str.len().to_string()).unwrap(),
|
||||
);
|
||||
|
||||
self.http
|
||||
|
||||
Reference in New Issue
Block a user