feat: 拆分 worker 架构并增强 assistant 安全保护

- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议

- 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险

- 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则

- 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
2026-07-10 00:41:36 +08:00
parent 7deae286ca
commit af6cfdaa83
70 changed files with 5953 additions and 1608 deletions
+11 -1
View File
@@ -23,6 +23,8 @@ interface MailConfig {
mailbox: string;
notify_account_id: string;
notify_user_id: string;
notification_subject_keywords: string[];
trusted_senders: string[];
poll_seconds: number;
fetch_limit: number;
accept_invalid_certs: boolean;
@@ -78,6 +80,8 @@ export function ConfigsPage() {
if (!t) return "-";
return new Date(t).toLocaleString("zh-CN", { timeZone: "UTC" }) + " UTC";
};
const fmtList = (values?: string[], empty = "-") =>
values && values.length > 0 ? values.join(", ") : empty;
return (
<div>
@@ -185,6 +189,12 @@ export function ConfigsPage() {
<dd className="text-gray-700 break-all">{item.notify_account_id}</dd>
<dt className="text-gray-400"></dt>
<dd className="text-gray-700 break-all">{item.notify_user_id}</dd>
<dt className="text-gray-400"></dt>
<dd className="text-gray-700 break-all">
{fmtList(item.notification_subject_keywords, "全部推送")}
</dd>
<dt className="text-gray-400"></dt>
<dd className="text-gray-700 break-all">{fmtList(item.trusted_senders)}</dd>
<dt className="text-gray-400"></dt>
<dd className="text-gray-700">{item.poll_seconds}s</dd>
<dt className="text-gray-400"></dt>
@@ -210,4 +220,4 @@ export function ConfigsPage() {
</section>
</div>
);
}
}
+25 -3
View File
@@ -24,6 +24,12 @@ interface MailMessage {
interface MailDetailResponse {
success: boolean;
message: MailMessage;
rendering?: MailRenderingSettings;
}
interface MailRenderingSettings {
trusted_sender: boolean;
remote_resources_allowed: boolean;
}
function formatBytes(bytes: number): string {
@@ -50,7 +56,7 @@ function contentIsHtml(content: string): boolean {
);
}
function sandboxedEmailHtml(content: string): string {
function sandboxedEmailHtml(content: string, allowRemoteResources: boolean): string {
const parsed = new DOMParser().parseFromString(content, "text/html");
const styles = Array.from(parsed.head.querySelectorAll("style"))
.map((style) => style.textContent || "")
@@ -58,8 +64,9 @@ function sandboxedEmailHtml(content: string): string {
.join("\n");
const body = parsed.body.innerHTML.trim() || content;
const styleTag = styles ? `<style>${styles}</style>` : "";
const imageSources = allowRemoteResources ? "data: cid: https: http:" : "data: cid:";
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data: cid: https: http:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'">${styleTag}</head><body>${body}</body></html>`;
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${imageSources}; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'">${styleTag}</head><body>${body}</body></html>`;
}
function renderTextContent(content: string) {
@@ -119,6 +126,9 @@ export function MailDetailPage() {
? new Date(msg.received_at).toLocaleString("zh-CN", { timeZone: "UTC" }) + " UTC"
: "-";
const htmlContent = contentIsHtml(msg.content);
const rendering = data.rendering;
const trustedSender = rendering?.trusted_sender === true;
const remoteResourcesAllowed = rendering?.remote_resources_allowed === true;
return (
<div>
@@ -146,6 +156,13 @@ export function MailDetailPage() {
<span className="font-semibold text-gray-700"></span>
{msg.source_key}
</span>
<span
className={`inline-flex items-center px-2 py-0.5 rounded-md text-xs font-semibold ${
trustedSender ? "bg-green-50 text-green-700" : "bg-amber-50 text-amber-700"
}`}
>
{trustedSender ? "可信发件人" : "未信任发件人"}
</span>
</div>
</div>
@@ -187,9 +204,14 @@ export function MailDetailPage() {
{htmlContent ? (
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
{!remoteResourcesAllowed && (
<div className="px-4 md:px-5 py-3 text-sm text-amber-700 bg-amber-50 border-b border-amber-100">
</div>
)}
<iframe
sandbox=""
srcDoc={sandboxedEmailHtml(msg.content)}
srcDoc={sandboxedEmailHtml(msg.content, remoteResourcesAllowed)}
className="w-full min-h-[420px] border-0 block bg-white"
loading="lazy"
title="邮件内容"