xsun_chat 系统架构设计
(本项目主要用于测试新技术开发,包括spec,harness(maker-reviewer),loop engineering)
目录
- 设计目标
- 整体架构
- 后端分层设计
- 双模式执行架构
- [SSE 事件系统](#SSE 事件系统)
- 图片消息与多模态
- 数据库设计
- 前端架构
- 安全设计
- [LLM 网关设计](#LLM 网关设计)
- [Tauri 桌面集成](#Tauri 桌面集成)
- 关键技术决策
设计目标
xsun_chat (The Doer) 的核心设计理念是:让 AI 不仅能回答问题,还能自主执行任务。系统围绕以下设计目标构建:
- 自主执行 --- AI 自主规划步骤、调用工具、检查结果,无需人工逐步指导
- 独立审核 --- 用独立的模型审核执行结果,避免"自己审自己"的盲点
- 安全可控 --- 沙盒隔离 + 白名单 + 高危确认,确保 AI 不会误操作系统
- 可观测性 --- 全链路 SSE 推送,实时展示思考过程、工具调用、审核进度
- 多模式适应 --- Chat 模式处理简单对话,Spec 模式处理复杂多步骤任务
- 桌面原生 --- Tauri 轻量壳,Web 端和桌面端共用同一套前后端代码
整体架构
┌──────────────────────────────────────────────────────────────┐
│ Tauri Desktop Shell │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Rust 原生层 │ │
│ │ · 自动检测 Python 解释器 │ │
│ │ · spawn 后端子进程 (uvicorn) │ │
│ │ · 进程生命周期管理 (窗口关闭 -> kill 后端) │ │
│ │ · 轮询 /health 端点等待后端就绪 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ WebView (Vue 3 SPA) │ │
│ │ ┌──────────┐ ┌──────────────────┐ ┌───────────────┐ │ │
│ │ │ Sidebar │ │ ChatView │ │ ContextPanel │ │ │
│ │ │ 工作区树 │ │ 对话流 + Spec卡片 │ │ 阶段/工具/审核│ │ │
│ │ └──────────┘ └──────────────────┘ └───────────────┘ │ │
│ │ Pinia Stores (chat/session/spec/workspace) │ │
│ │ useSSE composable (Fetch Stream Reader) │ │
│ └──────────────────────┬──────────────────────────────────┘ │
│ │ HTTP REST + SSE │
│ ┌──────────────────────▼──────────────────────────────────┐ │
│ │ FastAPI Backend (127.0.0.1:8787) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ API 层 (REST + SSE) │ │ │
│ │ │ /chat/send /chat/stop /sessions /settings │ │ │
│ │ │ /spec/confirm /spec/continue /workspaces │ │ │
│ │ └──────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────▼───────────────────────────┐ │ │
│ │ │ Core 编排层 │ │ │
│ │ │ Orchestrator | SpecOrchestrator │ │ │
│ │ │ MakerAgent | ReviewerAgent | Harness │ │ │
│ │ │ SpecGenerator | LoopEngineer │ │ │
│ │ │ ContextManager | PromptBuilder │ │ │
│ │ └──────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────▼───────────────────────────┐ │ │
│ │ │ Harness 基础设施层 │ │ │
│ │ │ LLMGateway | ToolRegistry | Sandbox | Memory │ │ │
│ │ └──────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────▼───────────────────────────┐ │ │
│ │ │ Tools 层 + DB 层 │ │ │
│ │ │ file_ops / shell / web | SQLite + Repository │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
通信路径:
| 方向 | 协议 | 说明 |
|---|---|---|
| 前端 -> 后端 | HTTP REST | 会话 CRUD、配置管理、Spec 确认 |
| 前端 -> 后端 | HTTP POST + SSE | 对话消息发送,流式接收事件 |
| Tauri Rust -> 后端 | 进程管理 | spawn/kill Python 进程,轮询 health |
| 后端 -> LLM 供应商 | HTTPS (OpenAI SDK) | 流式/非流式 LLM 调用 |
| 后端 -> 本地文件系统 | 文件 I/O + 子进程 | 工具执行(read/write/shell) |
后端分层设计
后端采用经典的四层架构,上层依赖下层,下层不感知上层:
1. API 层 (app/api/)
负责 HTTP 请求路由、参数校验、响应格式化。所有 API 通过 router.py 统一注册。
| 模块 | 端点 | 职责 |
|---|---|---|
chat.py |
POST /api/chat/send |
SSE 流式对话入口,根据 mode 参数路由到 Chat/Spec 编排器 |
chat.py |
POST /api/chat/stop |
取消当前执行任务 |
chat.py |
POST /api/chat/approve |
高危操作确认放行 |
chat.py |
POST /api/chat/path-access |
路径授权决定 |
chat.py |
POST /api/chat/export |
会话导出:全量消息(含思考链/工具调用/审核卡片)渲染为 Markdown |
chat.py |
POST /api/chat/upload-image |
图片上传:浏览器 File 落盘到附件目录(≤8MB) |
chat.py |
GET /api/chat/file-preview |
文件预览(附件目录持久授权,重启后仍可读) |
chat.py |
POST /api/chat/open-file |
在本地系统中打开输出文件 |
chat.py |
GET /api/chat/output-files |
查询会话输出文件列表 |
sessions.py |
CRUD /api/sessions |
会话管理 |
settings.py |
GET/PUT /api/settings |
全局配置管理 + 工作区设置 |
workspaces.py |
CRUD /api/workspaces |
多工作区注册与管理 |
spec.py |
POST /api/spec/confirm |
Spec 确认后继续执行 |
skills.py |
CRUD /api/skills |
技能管理(安装/卸载/代码/进化日志/Hub 配置) |
scheduler.py |
CRUD /api/scheduler/tasks |
定时任务管理(创建/启停/补跑/删除) |
mcp.py |
CRUD /api/mcp/servers |
MCP 服务器管理(注册/刷新/删除) |
router.py |
- | API 路由统一注册 |
关键设计:chat.py 在模块级别维护 _orchestrator 单例和 _current_task 引用,通过 asyncio.Queue 实现编排层到 HTTP 层的事件传递。
2. Core 编排层 (app/core/)
这是系统的核心大脑,包含所有 Agent 和编排逻辑:
| 组件 | 职责 |
|---|---|
Orchestrator |
Chat 模式编排器:判定复杂度 -> Maker 执行 -> Reviewer 审核 -> 修正重试 |
SpecOrchestrator |
Spec 模式编排器:Spec 生成 -> Loop 拆解 -> 逐步执行 + Harness 验证 |
MakerAgent |
执行层 Agent,在单会话上下文中完成 LLM 推理 + 工具调用循环(最多 N 轮) |
ReviewerAgent |
审核层 Agent,用独立模型审核 Maker 的完整执行轨迹 |
Harness |
单步验证器,在 Spec 模式中对每个 LoopStep 独立验证 |
SpecGenerator |
调用 LLM 流式生成结构化 Markdown Spec 文档 |
LoopEngineer |
将 Spec 文档拆解为可执行的 LoopStep 列表(优先结构化解析,降级 LLM 拆解) |
ContextManager |
构建 LLM 上下文窗口(系统提示词 + 最近 N 条历史 + 新消息),含上下文完整性校验 :自动修复会话中断导致的 tool_calls ↔ tool 配对损坏 |
PromptBuilder |
所有角色的系统提示词模板(Maker/Reviewer/SpecGenerator/LoopEngineer/Harness),Maker 提示词注入当前工作区路径引导文件生成位置 |
3. Harness 基础设施层 (app/harness/)
为 Core 层提供可复用的基础能力:
| 组件 | 职责 |
|---|---|
LLMGateway |
多供应商统一 LLM 网关,支持流式/非流式调用、429 自动切换、500 重试 |
ToolRegistry |
工具注册中心,管理 Tool 协议实现,提供 OpenAI function calling 格式转换 |
Sandbox |
Windows 安全沙盒:路径白名单、Shell 命令黑名单、高危操作识别 |
Memory |
会话记忆管理:历史消息加载、上下文窗口截断、Token 估算 |
4. Tools 层 + DB 层
| 组件 | 职责 |
|---|---|
registry.py |
工具统一注册入口(50+ 个工具)+ 工具分类元数据 + 外挂技能/MCP 动态加载 |
common.py |
路径校验(check_path_access)+ 输出文件追踪(OUTPUT_TOOLS 白名单 + output_file 通用约定) |
file_ops.py |
read_file / write_file / list_directory 工具 |
file_manager.py |
copy_file / move_file / delete_file / create_directory / search_files / file_info / append_file |
text_tools.py |
search_in_files / replace_in_file / diff_files 文本搜索替换 |
documents.py |
Excel/Word/CSV/PDF/PPT 读写(openpyxl / python-docx / pypdf / fpdf2 / python-pptx,惰性导入) |
image_tools.py |
convert_image / image_info 图片处理(Pillow) |
archive_tools.py |
zip_files / unzip_file 压缩解压 |
code_tools.py |
run_python / git_operation 开发工具 |
git_tools.py |
git_commits 提交历史查询 |
shell.py |
execute_shell 工具(PowerShell,含沙盒校验) |
web.py |
web_fetch 工具(httpx 异步 HTTP 客户端) |
web_tools.py |
web_search(DuckDuckGo)/ download_file 下载 |
jira_tools.py |
jira_unfinished_issues / jira_worklogs / jira_log_work Jira 集成 |
system_tools.py |
calculator / get_datetime / clipboard / system_info |
database.py |
SQLite 连接管理(WAL 模式),支持工作区隔离 + 中央注册表 |
repository.py |
数据访问层,封装所有 CRUD 操作 |
高危工具(delete_file / move_file / replace_in_file / git_operation 写操作 / 高危 Shell)通过 needs_approval 挂起执行,前端弹窗确认后经 POST /api/chat/approve 放行,Maker 以 _approved=True 重新调用该工具完成实际执行。
5. Skills 层(外挂技能系统,app/skills/)
统一技能框架,支持三种技能来源并存:内置工具 (静态 import)、外挂技能 (~/.xsun_chat/skills/installed/ 动态加载)、MCP 工具 (远程服务器)。外挂技能与内置工具实现同一 Tool 协议,对 Maker 透明。
| 组件 | 职责 |
|---|---|
models.py |
SkillMeta(skill.json 协议)/ HubSkillInfo / EvolutionRecord 数据模型 |
security.py |
AST 静态安全扫描:模块黑名单(subprocess/socket/ctypes 等)、危险调用(eval/exec/os.system)、反射属性(__globals__ 等)+ Tool 协议校验 |
loader.py |
importlib 动态加载,独立模块命名空间(xsun_skill_*),支持热重载(清除 __pycache__ 防止秒级 mtime 缓存穿透),skill.json 为注册键唯一权威来源 |
manager.py |
生命周期管理单例:安装(zip/源码)/ 卸载 / 版本备份(.versions/,保留最近 5 版)/ 进化日志(evolution_log.json,保留 500 条) |
auto_route.py |
技能协作路由引擎:解析式路由确定性优先 (解析 main.py 代码引用 / description 关系词 / SKILL.md 依赖字段),LLM 仅作兜底;create_skills_batch 批量创建后自动写回显式路由 |
hub_client.py |
SkillHub 客户端:{hub_url}/index.json 索引协议,支持 http(s) 静态托管与本地目录两种 Hub,SHA256 校验,搜索/下载/安装 |
self_mod.py |
自修改引擎:创建/修改流水线(验证 → 安装 → 试加载 → 热注册到当前 ToolRegistry),修改失败自动回滚 |
builtin_tools.py |
Agent 可调用的 6 个技能管理工具:create_skill / modify_skill / delete_skill / list_skills / search_skill_hub / install_skill_from_hub |
技能包结构 (skill.json + main.py):
data/skills/ # 应用数据根目录(<安装目录或项目根>/data)
├── installed/<name>/
│ ├── skill.json # 元数据:name/version/description/category/parameters_schema/sandbox_required
│ ├── main.py # Tool 协议实现(入口类自动探测或 entry_class 指定)
│ └── .versions/ # 历史版本备份(自动滚动保留 5 版)
├── cache/ # Hub 下载缓存
└── evolution_log.json # 自进化日志
自进化闭环 :Maker 发现工具缺失 → search_skill_hub 查 Hub(可安装)→ 或 create_skill 生成代码 → 安全扫描 + 协议校验 → 用户审批(skill_auto_install=false 时)→ 写入技能目录 → 热注册到当前 ToolRegistry → 同一会话下一次 LLM 调用即可用 (to_openai_tools() 每迭代重建)。安全双保险:安装期 AST 静态扫描 + 运行时 Sandbox 路径白名单。
SkillHub 协议 :静态托管友好(GitHub Raw / 任意静态服务器 / 本地目录),index.json 含技能列表与 download_url(相对/绝对)、SHA256;项目内 hub/ 目录为示例 Hub(python hub/build_hub.py 打包并生成索引)。
API :GET /api/skills(已安装列表)、GET/PUT /api/skills/hub-config、GET /api/skills/hub/search、POST /api/skills/hub/install、POST /api/skills/install-local、GET /api/skills/{name}/code、POST /api/skills/{name}/reload、DELETE /api/skills/{name}、GET /api/skills/evolution-log。
Hub 多协议 :客户端自动探测协议------xsun 格式(index.json,可安装)与 ClawHub 格式(/api/v1/skills,OpenClaw 官方仓库,其技能为指令格式与 xsun 工具不兼容,仅浏览)。默认 Hub 为项目自带 hub/ 目录(开箱即用)。
6. Scheduler 层(定时任务系统,app/scheduler/)
| 组件 | 职责 |
|---|---|
cron.py |
5 段 cron 解析器(* */n a-b a-b/n 列表,周日 0/7 归一,POSIX 日周规则)+ next_run 计算 + describe_cron 人类可读描述 |
models.py |
ScheduledTask:name/prompt/cron/workspace_path/enabled/运行状态 |
store.py |
JSON 持久化(data/scheduled_tasks.json,原子写入),CRUD + 运行结果记录(自动推进 next_run_at) |
engine.py |
asyncio 后台循环(15s tick):到期触发 → 工作区匹配检查 → 繁忙错峰(推迟 60s,上限 30min)→ 无头执行 Maker(新会话 ⏰ {name})→ 记录结果;随 FastAPI lifespan 启停,串行执行 |
builtin_tools.py |
Agent 工具:create_scheduled_task / list_scheduled_tasks / delete_scheduled_task / run_scheduled_task_now(写操作需审批) |
无头执行语义:注入"无用户交互"前缀引导 AI 非交互执行;工作区未打开时标记 skipped;需要审批的操作等待 5 分钟后自动拒绝(安全网)。API :GET/POST /api/scheduler/tasks、POST /api/scheduler/tasks/{id}/toggle、POST /api/scheduler/tasks/{id}/run、DELETE /api/scheduler/tasks/{id}。
双模式执行架构
系统支持两种执行模式,共享同一套 LLM 网关和工具系统:
Chat 模式数据流
用户消息
│
▼
Orchestrator.run(session_id, user_message, event_queue)
│
├─ 1. 保存用户消息到 DB
│
├─ 2. _should_review() 判断审核需求
│ ├─ "never" -> 跳过审核
│ ├─ "always" -> 始终审核
│ └─ "complex_only" -> 执行后判断:命中关键词 或 实际调用了写文件类工具
│
├─ 3. MakerAgent.run()
│ ├─ ContextManager.build_context() -> messages[]
│ │ └─ 上下文完整性校验:修复中断导致的 tool_calls 配对损坏
│ └─ for iteration in range(max_loop_iterations):
│ ├─ llm.chat_stream(messages, tools) -> SSE 事件流
│ ├─ if tool_calls: 执行工具 -> 结果追加到 messages
│ │ └─ execute_shell/run_python: 工作区快照差异检测 -> 登记新增文件
│ └─ else: 最终回复 -> return MakerResult
│
├─ 4. if needs_review: ReviewerAgent.review()
│ ├─ 构建审核内容(用户目标 + 执行轨迹 + 最终输出)
│ ├─ llm.chat(review_messages, reviewer_model, temp=0.3)
│ ├─ _parse_review() -> ReviewResult (passed/score/issues/feedback)
│ ├─ _persist_review() -> 持久化审核结果(所有分支均落库)
│ └─ if not passed: 修正重试(最多 1 轮)
│
└─ 5. event_queue -> SSE -> 前端
Spec 模式数据流
用户消息 (mode="spec")
│
▼
SpecOrchestrator.run_spec_mode()
│
├─ Phase 1: SpecGenerator.generate()
│ ├─ llm.chat_stream(messages, maker_model) -> 流式 Markdown
│ ├─ 保存 Spec 到 DB (status=draft)
│ ├─ 保存 spec_card 消息(metadata.type="spec_card")
│ └─ 发送 spec_ready 事件 -> SSE 连接断开,前端展示 Spec 卡片
│
▼ (用户点击「确认执行」,调用 POST /api/spec/confirm)
│
SpecOrchestrator.continue_after_confirmation()
│
├─ Phase 2: 更新 Spec status=confirmed
│
├─ Phase 3: LoopEngineer.decompose()
│ ├─ 优先:_parse_spec_markdown() 正则匹配 "### 步骤X: 标题" 格式
│ └─ 降级:_llm_decompose() 调用 LLM 输出 JSON
│
└─ Phase 4: _execute_loop_steps()
for each LoopStep:
├─ MakerAgent.run(step_description) -> MakerResult
├─ Harness.verify(step, maker_result) -> HarnessResult
├─ if not passed: 注入反馈 -> 重试(最多 2 次)
└─ 保存 HarnessResult 到 DB
降级策略总结:
| 场景 | 降级策略 |
|---|---|
| Spec 生成失败 | 降级为普通 Chat 模式 |
| Loop 拆解失败(结构化+LLM 都失败) | 返回单步骤"执行任务" |
| Harness 验证失败 | 重试 2 次后标记 failed,不阻塞后续步骤 |
| Reviewer 调用失败 | 降级为"通过"(score=80) |
| LLM 429 限流 | 自动切换备用供应商 |
SSE 事件系统
系统采用 asyncio.Queue 作为编排层到 HTTP 层的事件总线,所有事件通过 SSE 推送到前端。
事件类型一览
| 事件类型 | 触发时机 | 前端处理 |
|---|---|---|
phase |
阶段切换 (executing/reviewing/correcting/spec_generating/loop_decomposing) | 更新阶段指示器 |
thinking |
Maker 开始新一轮推理 | 展开思考链 |
thinking_delta |
LLM 推理过程流式文本片段 | 追加到思考链(重启后从历史恢复) |
text_delta |
LLM 流式输出文本片段 | 追加到 streamedContent |
tool_call_start |
工具调用开始 | 创建 ToolCall 条目 |
tool_call_result |
工具调用完成 | 更新 ToolCall 结果 |
approval_needed |
高危操作需确认 | 弹出确认对话框 |
path_access_needed |
路径未在白名单,需用户授权 | 弹出路径授权对话框 |
output_file |
输出文件被登记(路径+工具名) | 添加到右上角输出面板 |
review_start |
Reviewer 开始审核 | 显示审核中状态 |
review_result |
Reviewer 审核完成 | 渲染审核结果卡片 |
done |
Chat 模式执行完成 | 保存 AssistantMessage,清理状态 |
error |
发生错误 | 保存错误消息 |
spec_delta |
Spec 文档流式生成 | 追加到 SpecStore |
spec_ready |
Spec 生成完毕 | 展示 Spec 卡片,断开 SSE |
loop_steps_ready |
Loop 拆解完成 | 填充 SpecViewer 步骤列表 |
loop_step_start |
单步执行开始 | 高亮当前步骤 |
loop_step_result |
单步执行结果 | 更新步骤状态 (passed/failed) |
loop_step_retry |
单步重试 | 显示重试信息 |
harness_result |
Harness 验证完成 | 显示验证分数 |
loop_complete |
Loop 全部完成 | 显示执行摘要 |
事件流架构
┌─────────────┐ asyncio.Queue ┌──────────────┐ SSE ┌──────────┐
│ Core 编排层 │ ──────────────────> │ API 层 │ ──────────> │ 前端 │
│ (Producer) │ event_queue.put() │ (Consumer) │ text/stream│ (Reader) │
└─────────────┘ └──────────────┘ └──────────┘
│
asyncio.create_task()
_current_task 管理
支持取消 (task.cancel())
关键设计点:
- 异步解耦 :编排层通过
event_queue.put()生产事件,API 层通过event_queue.get()消费并格式化为 SSE - 超时保护 :
asyncio.wait_for(event_queue.get(), timeout=300)防止死等 - 取消机制 :
_current_task全局引用支持POST /chat/stop取消运行中的任务 - Spec 模式特殊处理 :收到
spec_ready后主动break断开 SSE,等待用户确认后重新连接
前端 SSE 消费
useSSE.connect()
│
├─ fetch(POST /chat/send, body:{session_id, content, mode})
├─ response.body.getReader() 逐块读取
├─ 解析 "data: {json}\n\n" 格式
└─ handleEvent() -> handlers 回调
├─ onTextDelta -> chatStore.streamedContent += content
├─ onToolCallStart -> chatStore.currentToolCalls.push(...)
├─ onSpecReady -> specStore.setSpecReady() + isGenerating=false
├─ onLoopComplete -> specStore.setLoopComplete() + save message
└─ onDone -> 保存 AssistantMessage + cleanup()
图片消息与多模态
对话支持发送图片附件(选择文件 / 拖拽 / 粘贴截图),并利用支持视觉的 LLM 理解图片内容。
附件上传与持久授权
| 组件 | 说明 |
|---|---|
POST /api/chat/upload-image |
浏览器场景(DOM File 无绝对路径):校验扩展名白名单(png/jpg/gif/bmp/webp/ico)与大小(≤8MB),落盘到 <data_root>/attachments/<session_id>/(UUID 命名),返回绝对路径 |
POST /api/chat/send (attachments) |
Tauri 场景附件为本地绝对路径,直接放入 attachments 字段,沙盒对本会话授权读写 |
GET /api/chat/file-preview |
附件目录前缀匹配规则:<data_root>/attachments/<session_id>/ 下的文件持久授权(替代内存授权,后端重启后历史图片仍可预览);其余路径走沙盒校验 + 输出文件判定 |
多模态上下文构建
_model_supports_vision(model):关键词检测视觉能力(gpt-4o/vision/-vl/gemini/claude/glm-4v/qwen-vl/llama-3.2等)_build_image_data_urls(attachments):将图片附件读取并转为 base64 data URL(单张 ≤4MB)ContextManager._build_user_message:构造 OpenAI 多模态 content 数组(text + image_url),仅当模型支持视觉时启用
前端展示
InputBox:附件按钮(Tauri 文件对话框 / 浏览器隐藏 file input)、拖拽上传、粘贴截图(clipboardData)FileRefCard:图片路径自动渲染缩略图(previewUrl),点击放大预览- AI 输出图片(工具产物)同样以缩略图展示
useApi.ts的BASE_URL:Tauri 环境http://127.0.0.1:8787/api,浏览器环境/api(Vite 代理)
数据库设计
ER 图
┌──────────┐ ┌──────────┐ ┌──────────┐
│ sessions │ 1───N │ messages │ │task_runs │
│ │ │ │ │ │
│ id │ │ id │ │ id │
│ title │ │session_id│ │session_id│
│ mode │ │ role │ │ goal │
│workspace │ │ content │ │ status │
│_path │ │ metadata │ │plan │
│created_at│ │created_at│ │review_ │
│updated_at│ └──────────┘ │result │
└──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌────────────────┐
│ specs │ 1───N │loop_steps│ 1───N │harness_results │
│ │ │ │ │ │
│ id │ │ id │ │ id │
│session_id│ │ spec_id │ │ step_id │
│ title │ │session_id│ │ passed │
│raw_ │ │step_index│ │ score │
│markdown │ │ title │ │ feedback │
│ status │ │description│ │ details(JSON) │
│created_at│ │expected_ │ │ created_at │
│updated_at│ │output │ └────────────────┘
└──────────┘ │ status │
│max_retries│
│depends_on│
│retry_count│
│error_msg │
└──────────┘
中央注册表(独立数据库 workspaces.db):
┌────────────┐
│ workspaces │
│ │
│ path (PK) │
│ name │
│ created_at │
│last_opened │
│_at │
└────────────┘
关键表说明
| 表 | 用途 | 关键字段 |
|---|---|---|
sessions |
会话记录 | mode (chat/spec), workspace_path |
messages |
消息历史 | metadata (JSON: tool_calls, tool_call_id, type=spec_card) |
task_runs |
任务执行记录 | plan (Spec 内容), review_result (审核 JSON) |
specs |
Spec 文档 | raw_markdown, status (draft/confirmed) |
loop_steps |
Loop 执行步骤 | status (pending/running/passed/failed), depends_on |
harness_results |
单步验证结果 | score, details (issues JSON) |
workspaces |
工作区注册表 | path (PK), last_opened_at |
数据库隔离策略
所有应用数据(配置、定时任务、技能、日志、数据库)统一存放在应用数据根目录 data/ 下
(开发模式:<backend 项目根>/data/;打包安装版:<安装目录>/data/;
旧版 ~/.xsun_chat 数据在首次启动时自动迁移并清理):
- 全局配置 :
data/config.json(JSON 文件) - 工作区注册表 :
data/central/xsun_chat_central.db(中央 SQLite) - 会话数据 :
data/workspaces/<文件夹名>_<路径哈希>/xsun_chat.db(每工作区独立) - 切换工作区 :
config.set_workspace(path)会同时更新workspace_path和db_dir,后端自动连接对应数据库
前端架构
技术栈
| 类别 | 技术 | 说明 |
|---|---|---|
| 框架 | Vue 3 (Composition API) | <script setup lang="ts"> |
| 语言 | TypeScript (strict) | 全量类型定义 |
| 状态管理 | Pinia | 6 个 Store (chat/session/spec/workspace/settings/taskflow) |
| 路由 | Vue Router 4 | / (主页), /setup (向导), /settings (设置) |
| HTTP | Fetch API | SSE 流读取 + REST JSON |
| Markdown | markdown-it | 代码高亮 (highlight.js) + 数学公式 (KaTeX/texmath) |
| 样式 | CSS Variables | 玻璃拟态暗色主题 (glassmorphism.css) |
| 桌面壳 | Tauri 2.x | WebView + Rust 进程管理 |
组件树
App.vue
├── AppLayout.vue
│ ├── Sidebar.vue
│ │ ├── 工作区列表(树形结构)
│ │ ├── 会话列表(按工作区分组)
│ │ └── 底部操作按钮(设置/新建)
│ ├── ChatView.vue
│ │ ├── MessageBubble.vue (用户/AI 消息)
│ │ │ ├── SpecCard.vue (Spec 卡片)
│ │ │ ├── TaskFlowCard.vue (工具调用卡片)
│ │ │ ├── ThinkingChain.vue (思考链)
│ │ │ ├── ToolCallLog.vue (工具调用日志)
│ │ │ └── ReviewResultCard.vue (审核结果)
│ │ ├── MarkdownRenderer.vue
│ │ └── InputBox.vue (输入框 + 模式切换)
│ └── ContextPanel.vue
│ ├── 当前阶段指示器
│ ├── 工具调用统计
│ ├── 审核结果摘要
│ └── SpecViewer.vue (Spec 进度追踪)
└── SetupWizard.vue (首次配置向导)
状态管理 (Pinia Stores)
stores/
├── chat.ts # 核心对话状态
│ ├── messages, isGenerating, currentPhase
│ ├── streamedContent, currentToolCalls, currentReview
│ ├── thinkingChain (思考链)
│ ├── sendMessage() -> SSE 连接 + 事件处理
│ ├── stopGeneration() -> abort + 保存中断消息
│ └── loadHistory() -> 从 DB 恢复消息 + Spec 状态
│
├── session.ts # 会话列表管理
│ └── sessions[], createSession(), renameSession(), deleteSession()
│
├── spec.ts # Spec 模式状态
│ ├── currentSpec, loopSteps, isGenerating
│ ├── setSpecReady() -> 展示 Spec 卡片
│ ├── appendSpecContent() -> 流式追加 Spec 内容
│ ├── setLoopSteps() -> 填充步骤列表
│ └── updateStepStatus() -> 更新每步状态
│
├── workspace.ts # 多工作区管理
│ └── workspaces[], currentWorkspace, switchWorkspace()
│
├── settings.ts # 全局配置状态
│ └── config, saveConfig(), testConnection()
│
└── taskflow.ts # 任务流可视化
└── 当前任务步骤、工具调用日志
数据流 (单向)
用户输入 -> InputBox.vue
│
▼
chatStore.sendMessage(content, mode)
│
├─ 添加 UserMessage 到 messages[]
├─ 设置 isGenerating=true
├─ useSSE.connect(sessionId, content, handlers, mode)
│ │
│ ├─ fetch POST /api/chat/send
│ ├─ reader.read() 循环
│ └─ handleEvent(event, handlers)
│ │
│ ├─ onTextDelta -> streamedContent (响应式)
│ ├─ onToolCallStart -> currentToolCalls[]
│ ├─ onSpecReady -> specStore (跨 Store)
│ ├─ onLoopStepResult -> specStore.updateStepStatus()
│ └─ onDone -> 构建 AssistantMessage -> messages[]
│
└─ cleanup() -> isGenerating=false
安全设计
沙盒三层防御
用户请求
│
▼
第一层: 路径白名单 (Sandbox.validate_path)
├─ 默认仅允许用户主目录
├─ 禁止系统目录 (C:\Windows, C:\Program Files, ...)
├─ 禁止 UNC 网络路径
├─ 可通过 sandbox_allowed_dirs 扩展
└─ 工作区路径自动加入白名单
│
▼
第二层: Shell 命令黑名单 (Sandbox.validate_shell_command)
├─ 拦截危险命令: Remove-Item -Recurse, Format-Volume,
│ diskpart, reg delete, bcdedit, ...
└─ 拦截编码命令: powershell -EncodedCommand
│
▼
第三层: 高危操作确认 (Sandbox.needs_approval)
├─ write_file 覆盖已有文件 -> 弹窗确认
├─ execute_shell 含 Remove-Item/del/rmdir/move -> 弹窗确认
└─ 确认超时 (300s) 自动拒绝
│
▼
第四层: 路径访问授权 (API path-access)
├─ 工具首次访问白名单外路径时,弹出授权对话框
├─ 用户可选择"允许本次" / "加入白名单" / "拒绝"
└─ 授权决策通过 SSE approval_needed 事件推送到前端
LLM 网关容错
LLM 调用
│
├─ 400 Bad Request
│ └─ 含 "context_length" -> 抛出 LLMContextLengthError
│ -> ContextManager 截断历史后重试
│
├─ 429 Rate Limit
│ └─ 自动查找备用供应商 -> 切换重试
│
├─ 500 Server Error
│ └─ 重试 2 次(间隔 1s, 3s)
│
└─ 超时 (60s connect, 10s read)
└─ 通知前端并取消
上下文完整性保护
ContextManager.build_context() 在构建 LLM 上下文时执行双向修复:
- 缺失 tool 响应 :assistant 消息含
tool_calls但缺少对应tool消息 → 注入占位[执行被中断]消息 - 孤儿 tool 消息 :
tool消息的tool_call_id找不到对应tool_calls→ 移除该消息
这确保了会话中断/窗口截断后,OpenAI API 的 tool_calls ↔ tool 严格配对要求始终满足。
配置安全
- API Key 存储在
data/config.json(应用数据根目录,已被.gitignore排除),不提交到版本控制 - 配置修改通过 API 层校验(maker/reviewer 的 provider 必须在 providers 列表中)
- Sandbox 白名单通过配置动态更新,无需重启
LLM 网关设计
多供应商架构
LLMGateway
│
├─ _clients: dict[str, AsyncOpenAI]
│ ├─ "deepseek" -> AsyncOpenAI(base_url="https://api.deepseek.com")
│ ├─ "openai" -> AsyncOpenAI(base_url="https://api.openai.com/v1")
│ ├─ "qwen" -> AsyncOpenAI(base_url="https://dashscope.aliyuncs.com/...")
│ └─ "custom" -> AsyncOpenAI(base_url=任意 OpenAI 兼容地址)
│
├─ _provider_map: dict[str, LLMProviderConfig]
│ └─ 每个供应商的默认模型名
│
└─ 角色模型解析:
├─ get_maker_config() -> (client, model, temperature=0.7)
└─ get_reviewer_config() -> (client, model, temperature=0.3)
流式处理 (chat_stream)
async for chunk in llm.chat_stream(messages, tools, provider, model, temp):
│
├─ client.chat.completions.create(stream=True, stream_options={include_usage:True})
│
└─ _process_stream(stream):
async for chunk in stream:
├─ delta.content -> yield LLMStreamChunk(type="text_delta", content=...)
└─ delta.tool_calls -> 缓冲拼接 arguments
└─ finish_reason="tool_calls" -> yield LLMStreamChunk(type="tool_call", ...)
关键设计:
- tool_calls 流式拼接 :OpenAI 流式 API 会将 tool call 的 arguments 分多个 chunk 发送,需要在
_process_stream中按 index 缓冲拼接 - 空 tool_calls 过滤 :
ContextManager.build_context()恢复历史消息时,仅当tool_calls非空数组时才附加该字段(OpenAI API 不允许空数组) - spec_card 消息过滤 :构建上下文时跳过
metadata.type="spec_card"的消息(content 为空,不应参与 LLM 推理)
调用模式对比
| 特性 | chat() 非流式 |
chat_stream() 流式 |
|---|---|---|
| 使用场景 | Reviewer/Harness/LoopEngineer | Maker/SpecGenerator |
| 429 处理 | 切换备用供应商重试 | 切换备用供应商重试 |
| 500 处理 | 重试 2 次 | 重试 2 次 |
| 返回类型 | LLMResponse |
AsyncGenerator[LLMStreamChunk] |
Tauri 桌面集成
架构
Tauri 应用启动
│
├─ lib.rs: run()
│ ├─ resolve_backend_dir()
│ │ ├─ 生产模式: exe_dir/../backend/ (sidecar)
│ │ └─ 开发模式: src-tauri/../../backend/
│ │
│ ├─ launch_backend()
│ │ ├─ find_python() (查找 python/python3/py)
│ │ └─ Command::new(python).args(["-m","uvicorn","app.main:app",...])
│ │ .current_dir(backend_dir).spawn()
│ │
│ ├─ app.manage(BackendProcess(Mutex<Option<Child>>))
│ │ └─ Drop 时自动 kill 后端进程
│ │
│ └─ tauri::async_runtime::spawn(wait_for_backend_ready())
│ └─ 轮询 http://127.0.0.1:8787/health (最多 15s)
│
└─ 加载 WebView (Vue 3 SPA)
前后端通信
typescript
// useApi.ts 自动检测环境(window.isTauri / __TAURI_INTERNALS__)
const BASE_URL = isTauriEnv()
? 'http://127.0.0.1:8787/api' // Tauri 桌面模式(无 Vite 代理,需完整地址)
: '/api' // 浏览器开发模式(Vite 代理 /api 到后端)
打包模式
| 模式 | 说明 | 产物 |
|---|---|---|
开发 (tauri:dev) |
Tauri 自动管理前后端进程 | 桌面窗口 + 热重载 |
系统 Python (tauri:build) |
依赖用户 Python 环境 | .msi / .nsis.exe |
PyInstaller (build_exe.py) |
Python 编译为独立 exe | 独立 .exe (无需 Python) |
关键技术决策
1. 为什么 Maker 和 Reviewer 必须用不同模型?
同一个 LLM 审核自己的输出存在系统性盲点------它倾向于认为自己的推理是正确的。通过引入独立的 Reviewer 模型(不同供应商或不同模型),实现了真正的交叉验证。这是系统最核心的设计理念。
2. 为什么使用 SSE 而非 WebSocket?
- 单向推送:系统只需要服务端推送到前端,不需要前端频繁向服务端发消息
- 更简单的实现:SSE 基于 HTTP,无需额外的握手协议
- 更好的兼容性:SSE 在 Tauri WebView 中无需特殊配置
- 取消机制 :通过
AbortController和asyncio.Task.cancel()实现优雅取消
3. 为什么 Chat 和 Spec 是两个独立的 Orchestrator?
Chat 模式强调"快速响应 + 整体审核",而 Spec 模式强调"精细控制 + 逐步验证"。两者的执行流程、事件类型和用户体验完全不同。分离为两个 Orchestrator 避免了代码中大量的 if-else 分支,各自独立演进。
4. 为什么 LoopEngineer 优先用正则解析而非 LLM?
- 确定性 :Spec 格式由
SpecGenerator的 prompt 控制,输出格式稳定 - 速度:正则解析是 O(n) 的,无需网络调用
- 成本:避免为拆解步骤额外消耗 token
- 降级保障:仅在正则无法匹配时降级为 LLM 拆解
5. 为什么选择 SQLite + WAL 模式?
- 零配置:无需安装数据库服务,符合桌面应用定位
- WAL 模式:支持并发读(一个写 + 多个读),满足 SSE 推送时的并发需求
- 工作区隔离 :每个工作区独立的
.db文件,天然支持数据隔离和备份 - 体积小:无需引入 PostgreSQL/MySQL 等重型依赖
6. 降级优先的设计哲学
系统在多个层面实现了降级策略:
- Reviewer 故障 -> 降级通过,不阻塞执行
- Harness 故障 -> 降级通过,不阻塞 Loop
- Spec 生成失败 -> 降级为 Chat 模式
- Loop 拆解失败 -> 降级为单步骤
- LLM 限流 -> 自动切换备用供应商
核心理念:审核层/验证层的故障不应该阻塞执行层,用户始终能得到结果(附带降级说明)。
7. 为什么 Tauri 而非 Electron?
- 包体积:Tauri 应用 < 10MB(不含 Python 后端),Electron 通常 > 100MB
- 内存占用:Tauri 使用系统 WebView,比内嵌 Chromium 轻量
- Rust 安全性:进程管理和系统调用利用 Rust 的内存安全保证
- Python 后端独立性:后端和前端完全解耦,可独立升级或替换