feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议 - 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险 - 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则 - 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
+3
-2
@@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "ias-http"
|
||||
version = "0.1.15"
|
||||
version = "0.1.17"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ias-common = { path = "../ias-common" }
|
||||
anyhow = "1"
|
||||
axum = "0.8.9"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -15,6 +16,6 @@ sqlx = { version = "0.9.0", features = [
|
||||
"postgres",
|
||||
"chrono",
|
||||
] }
|
||||
tokio = { version = "1.0", features = ["net", "sync"] }
|
||||
tokio = { version = "1.0", features = ["net", "sync", "io-util"] }
|
||||
tower-http = { version = "0.6", features = ["cors", "fs"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
|
||||
@@ -166,8 +166,19 @@ fn api_groups() -> Vec<ApiGroupDoc> {
|
||||
"邮件详情",
|
||||
"已读状态",
|
||||
"邮箱目录过滤",
|
||||
"可信发件人渲染",
|
||||
],
|
||||
keywords: &[
|
||||
"mail",
|
||||
"email",
|
||||
"邮件",
|
||||
"邮箱",
|
||||
"未读",
|
||||
"已读",
|
||||
"搜索邮件",
|
||||
"可信邮箱",
|
||||
"邮件渲染",
|
||||
],
|
||||
keywords: &["mail", "email", "邮件", "邮箱", "未读", "已读", "搜索邮件"],
|
||||
},
|
||||
ApiGroupDoc {
|
||||
id: "console",
|
||||
@@ -420,13 +431,17 @@ fn api_docs() -> Vec<ApiDoc> {
|
||||
method: "GET",
|
||||
path: "/v1/mail/messages/{id}",
|
||||
summary: "读取单封邮件记录",
|
||||
description: "返回单封邮件详情;如果邮件尚未标记已读,读取时会尝试标记为已读。",
|
||||
description: "返回单封邮件详情和渲染策略;如果邮件尚未标记已读,读取时会尝试标记为已读。",
|
||||
auth: "无",
|
||||
query_params: &[],
|
||||
path_params: &["id: 邮件记录 id"],
|
||||
body: None,
|
||||
response: r#"{"success":true,"message":{"id":1,"from_label":"...","subject":"...","content":"...","seen":true}}"#,
|
||||
notes: &["邮件不存在时返回 404。"],
|
||||
response: r#"{"success":true,"message":{"id":1,"from_label":"...","subject":"...","content":"...","seen":true},"rendering":{"trusted_sender":true,"remote_resources_allowed":true}}"#,
|
||||
notes: &[
|
||||
"rendering.trusted_sender 根据邮件账号可信发件人列表和 From 字段计算。",
|
||||
"remote_resources_allowed=false 时前端会阻止 HTML 邮件中的远程图片资源。",
|
||||
"邮件不存在时返回 404。",
|
||||
],
|
||||
},
|
||||
ApiDoc {
|
||||
id: "console.tools.list",
|
||||
@@ -451,7 +466,7 @@ fn api_docs() -> Vec<ApiDoc> {
|
||||
method: "GET",
|
||||
path: "/v1/user-configs",
|
||||
summary: "查询用户侧配置概览",
|
||||
description: "返回微信账号和邮件账号配置的非敏感概览,密码或 token 只返回是否已配置。",
|
||||
description: "返回微信账号和邮件账号配置的非敏感概览,邮件账号会包含推送标题关键字和可信发件人列表,密码或 token 只返回是否已配置。",
|
||||
auth: "无",
|
||||
query_params: &[],
|
||||
path_params: &[],
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{any, get};
|
||||
use axum::{Json, Router};
|
||||
use ias_common::control::{ControlResponse, RuntimePaths};
|
||||
use sqlx::PgPool;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::UnixStream;
|
||||
use tokio::sync::watch;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_http::services::{ServeDir, ServeFile};
|
||||
@@ -26,6 +29,7 @@ pub async fn create_http(
|
||||
|
||||
let app = Router::new()
|
||||
.route("/v1/health", get(health))
|
||||
.route("/v1/daemon/status", get(daemon_status))
|
||||
.merge(api_doc_routes::<()>())
|
||||
.merge(log_routes::<()>(db.clone()))
|
||||
.merge(update_routes::<()>(db.clone()))
|
||||
@@ -66,6 +70,39 @@ async fn health() -> Json<serde_json::Value> {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn daemon_status() -> Json<serde_json::Value> {
|
||||
let paths = RuntimePaths::new();
|
||||
match request_daemon_status(&paths).await {
|
||||
Ok(snapshot) => Json(serde_json::json!({
|
||||
"success": true,
|
||||
"status": "ok",
|
||||
"daemon": snapshot,
|
||||
})),
|
||||
Err(error) => Json(serde_json::json!({
|
||||
"success": true,
|
||||
"status": "degraded",
|
||||
"message": "daemon control socket unavailable",
|
||||
"error": error.to_string(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_daemon_status(
|
||||
paths: &RuntimePaths,
|
||||
) -> anyhow::Result<ias_common::control::RuntimeSnapshot> {
|
||||
let mut stream = UnixStream::connect(&paths.socket_file).await?;
|
||||
stream.write_all(b"status\n").await?;
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).await?;
|
||||
match serde_json::from_str(line.trim())? {
|
||||
ControlResponse::Status { snapshot } => Ok(snapshot),
|
||||
ControlResponse::Ack { message } | ControlResponse::Error { message } => {
|
||||
anyhow::bail!(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn api_not_found() -> (StatusCode, Json<serde_json::Value>) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
|
||||
Reference in New Issue
Block a user