# 邮件驱动·多智能体协作平台 — MVP 技术规格书 > 基于 v2.0 完整设计文档,聚焦最小可用闭环。 > 版本:v0.1 | 日期:2026-09-01 --- ## 一、MVP 范围定义 ### 1.1 核心目标 **跑通一个完整的人-Agent协作闭环:** ``` 人类发任务 → Agent 收到 → Agent 执行 → Agent 遇到需要人决策的事 → 请求权限 → 人类批准 → Agent 继续执行 → Agent 回复结果 ``` ### 1.2 MVP 包含(✅)与不包含(❌) | 功能 | MVP | 说明 | |------|-----|------| | 邮件收发(人↔Agent) | ✅ | 核心闭环 | | 三维寻址(name@path.session) | ✅ | 核心寻址范式;session 位三态:省略=默认会话 / `new`=新建 / 别名=必须已存在(否则无法送达) | | 会话别名(命名与改名) | ✅ | 别名负责寻址,全局唯一;`new` 为保留字 | | 别名/标题复用 Agent 平台命名 | ✅ | 平台(如 opencode)由模型生成会话摘要标题 + slug,经 `POST /sessions/:id/sync` 回写;撞名自动加 `-2` 后缀 | | 密钥认证(Agent / 用户分离) | ✅ | agent_keys 用于注册/心跳/SSE;user_keys 仅用于 /me/*;三种生命周期 permanent/one_time/timed | | 内置 SQLite(外部库可选) | ✅ | 默认零依赖;`DATABASE_URL` 非空时切 PostgreSQL | | systemd 一键部署 | ✅ | `deploy/install.sh`,产物为一个二进制 + 一个 .db | | 附件 | ✅ | 内容寻址存盘(sha256 去重),上传与发信两步;下载强制 octet-stream | | 转发 | ✅ | 引用原文 + 附件随行;只能转发自己参与过的邮件 | | Agent 发信配额 | ✅ | 只限发信不限收信;剩余次数随响应与心跳回传 | | WebAPI 等价接入 | ✅ | WebUI 与第三方客户端同一套 API,见 docs/API.md | | 对话树 | ✅ | 沿 parent_mail_id 递归展开,跨会话,按方向分块加载,不建 tree_nodes 表 | | Agent 提议改会话名 | ✅ | 正文里的 HTML 注释标记,入库时剥除;改名需用户确认 | | 会话往返预算 | ✅ | 写信时给 / 对话页随时改;省略则取收件 Agent 的默认值 | | 新建会话速率限制 | ✅ | Agent 1 小时 20 条;堵住用 .new 绕过预算,人类不受限 | | 窄屏适配 | ✅ | <768px 改为页面覆盖 + 滑入动画;底部导航 + 抽屉侧栅 | | 插件自动转发 | ✅ | 平台原生权限询问 + 本轮最终总结;**不消耗配额** | | 会话管理(创建/列表/状态) | ✅ | 会话是协作的边界 | | 权限请求与决策 | ✅ | Agent 需要人批准才能继续 | | Agent 注册与发现 | ✅ | 最小 Registry | | 前端收件箱/发件箱列表 | ✅ | 基础 UI | | 前端新建邮件/回复 | ✅ | 基础 UI | | 邮件正文 Markdown 渲染 | ✅ | Agent 输出多为 MD | | Agent 桥接插件 | ✅ | opencode 为第一个接入平台(`plugins/opencode-mail-bridge`) | | 对话树 | ❌ | 后续迭代 | | 抄送(CC) | ✅ | 已实现:`cc_list` + 收件箱抄送可见 | | 转发 | ❌ | 后续迭代 | | 配额机制 | ❌ | 后续迭代 | | 会话别名命名/改名 | ✅ | 发信时命名、事后改名、平台命名自动同步 | | Agent 正文里主动提议改名 | ❌ | 后续迭代 | | 工作列表/卡片视图 | ✅ | 中间栏可切列表/卡片;卡片显示主题、最新进展、预算徽标 | | DeepSeek Harness 插件 | ❌ | 后续迭代 | | 跨主机 Agent 发现 | ❌ | 后续迭代 | --- ## 二、系统简化架构 ``` ┌─────────────────────────────────────────────────────┐ │ 前端(Web UI) │ │ 收件箱 / 发件箱 / 新建邮件 / 回复 │ │ Markdown 编辑器 + 渲染器 │ └──────────────────────┬──────────────────────────────┘ │ HTTP REST + SSE ▼ ┌─────────────────────────────────────────────────────┐ │ Mail Gateway(单体服务) │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────────┐ │ │ │ Mail API │ │ Registry │ │ Session Mgr │ │ │ │ 收发路由 │ │ 注册/发现 │ │ 会话/状态 │ │ │ └───────────┘ └───────────┘ └───────────────┘ │ │ ┌───────────────────────────────────────────────┐ │ │ │ Agent 通信层(WebSocket) │ │ │ └───────────────────────────────────────────────┘ │ └──────────────────────┬──────────────────────────────┘ │ WebSocket ▼ ┌─────────────────────────────────────────────────────┐ │ Agent 侧(Pi Agent + 插件) │ │ ┌─────────────────────────────────────────────┐ │ │ │ pi-mail-bridge 插件 │ │ │ │ - 注册 send_mail / read_inbox / req_perm │ │ │ │ - WebSocket 连接 Gateway │ │ │ │ - 新邮件注入 Agent 上下文 │ │ │ └─────────────────────────────────────────────┘ │ │ Pi Agent 本体(文件读写、终端、Git、推理) │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────┐ │ SQLite(默认) │ 邮件 + 会话持久化 │ 或外部 PostgreSQL│ DATABASE_URL 指定 └─────────────────┘ ``` **简化要点:** - Gateway 和 Registry 合并为单体服务 - 前端用 SSE(Server-Sent Events)替代 WebSocket(更简单,单向推送够用) - 数据库默认内置 SQLite(零外部依赖,与 go:embed 的前端一起构成「一个二进制 + 一个 .db」); `DATABASE_URL` 非空时切换到外部 PostgreSQL。方言差异收在 `internal/db`,repo 层只写一份 SQL - SSE 推送不依赖数据库通知机制(无需 Redis 或 PG LISTEN/NOTIFY):单体进程内 `sse.Manager` 按收件人名分流,Agent 通道与人类用户通道共用一套投递 --- ## 三、数据模型 以下 DDL 以 PostgreSQL 方言书写。SQLite 侧结构与语义完全一致,仅方言不同 (`UUID`→`TEXT`、`TIMESTAMPTZ`→`DATETIME`、`JSONB`→`TEXT`、`VARCHAR(n)`→`TEXT`), 见 `server/internal/db/migrations/init_sqlite.sql`。 ### 3.1 agents 表 ```sql CREATE TABLE agents ( agent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), agent_name VARCHAR(64) NOT NULL UNIQUE, -- 全局唯一 secret VARCHAR(128) NOT NULL, -- 注册密钥 host_url VARCHAR(256) NOT NULL, -- 插件通信地址(ws://...) workspaces JSONB NOT NULL DEFAULT '[]', -- [{name, path}] platform VARCHAR(32) NOT NULL DEFAULT 'pi', -- pi / dsh / cline status VARCHAR(16) NOT NULL DEFAULT 'offline', -- online / offline max_rounds INT NOT NULL DEFAULT 10, -- 通信配额(Phase 2 启用) used_rounds INT NOT NULL DEFAULT 0, last_seen TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); ``` ### 3.2 sessions 表 ```sql CREATE TABLE sessions ( session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_alias VARCHAR(128), -- 会话别名(如 add-health-check),全局唯一,负责寻址 from_agent VARCHAR(64) NOT NULL, -- 发起者 agent_name(或 'human') subject VARCHAR(512) NOT NULL, -- 会话主题 status VARCHAR(32) NOT NULL DEFAULT 'active', -- active / waiting / completed created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_sessions_alias ON sessions(session_alias); CREATE INDEX idx_sessions_status ON sessions(status); -- 别名负责三维寻址 name@path.,必须唯一;未命名会话(NULL)不受约束 CREATE UNIQUE INDEX idx_sessions_alias_uniq ON sessions(session_alias) WHERE session_alias IS NOT NULL; ``` ### 3.3 mails 表 ```sql CREATE TABLE mails ( mail_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL REFERENCES sessions(session_id), parent_mail_id UUID REFERENCES mails(mail_id), -- 回复链(线性,非树) from_name VARCHAR(64) NOT NULL, -- 发件人 agent_name(或 'human') from_workspace VARCHAR(128), -- 发件人工作区 to_name VARCHAR(64) NOT NULL, -- 收件人 agent_name(或 'human') to_workspace VARCHAR(128), -- 收件人工作区 subject VARCHAR(512) NOT NULL, body TEXT NOT NULL, -- Markdown mail_type VARCHAR(32) NOT NULL DEFAULT 'normal', -- normal / permission_request permission_options JSONB, -- 权限请求的选项 ["同意","拒绝"] permission_result VARCHAR(32), -- approved / rejected / null status VARCHAR(16) NOT NULL DEFAULT 'unread', -- unread / read / archived created_at TIMESTAMPTZ DEFAULT NOW(), -- 防循环(Phase 2 启用) hop_limit INT DEFAULT 5 ); CREATE INDEX idx_mails_session ON mails(session_id); CREATE INDEX idx_mails_to ON mails(to_name, status); CREATE INDEX idx_mails_parent ON mails(parent_mail_id); ``` ### 3.4 设计决策说明 | 决策 | 理由 | |------|------| | `parent_mail_id` 而非 `tree_node_id` | MVP 只有线性回复链,不需要树结构。Phase 2 引入 CC/转发后再加 tree 节点表 | | `from_name` 用字符串而非 UUID | MVP 阶段 agent_name 全局唯一,简化查询。Phase 2 可加 agent_id 外键 | | `permission_options` 存 JSONB | 允许每个权限请求自定义选项,前端动态渲染按钮 | | 不单独建 `conversations` 表 | session 已经承载会话概念,不重复建设 | --- ## 四、API 设计 ### 4.1 基础信息 - **Base URL**: `http://gateway:8080/api/v1` - **认证**: Agent 侧用 `X-Agent-Name` + `X-Agent-Secret` header;前端用 session cookie - **内容类型**: `application/json` ### 4.2 Agent 注册与心跳 ``` POST /agent/register Body: { name, secret, workspaces: [{name, path}], platform } Response: { agent_id, status: "registered" } POST /agent/heartbeat Header: X-Agent-Name, X-Agent-Secret Response: { status: "ok", pending_mails: 3 } ``` 心跳响应中 `pending_mails` 告诉插件有多少未读邮件需要拉取。 ### 4.3 邮件收发 ``` POST /mail/send Header: X-Agent-Name, X-Agent-Secret Body: { to: "builder@ModelRouter", -- name@workspace 格式 subject: "请为 ModelRouter 增加健康检查接口", body: "## 需求\n\n...", session_alias: "add-health-check", -- 可选,新建会话时用 reply_to: "uuid" -- 可选,回复某封邮件 } Response: { mail_id, session_id } GET /mail/inbox Header: X-Agent-Name, X-Agent-Secret Query: ?status=unread&limit=10 Response: { mails: [ { mail_id, session_id, session_alias, from_name, from_workspace, subject, body_preview, mail_type, status, created_at } ], total: 5 } GET /mail/:mail_id Header: X-Agent-Name, X-Agent-Secret Response: { ... 完整邮件字段 ... } POST /mail/:mail_id/read Header: X-Agent-Name, X-Agent-Secret Response: { status: "read" } ``` ### 4.4 权限请求 ``` POST /permission/request Header: X-Agent-Name, X-Agent-Secret Body: { to: "human", -- 固定为 human question: "是否允许合并 PR #42?", options: ["同意合并", "拒绝,需要修改"], context: "PR 改动了 3 个文件,CI 全部通过...", session_id: "uuid" -- 可选,关联现有会话 } Response: { mail_id, session_id, permission_mail_id } POST /permission/decide Body: { mail_id: "uuid", decision: "同意合并", -- 必须是 request 中 options 之一 note: "可以合并,但请补充测试" -- 可选备注 } Response: { status: "decided" } ``` ### 4.5 会话与 Agent 列表 ``` GET /sessions Query: ?status=active&limit=20 Response: { sessions: [ { session_id, session_alias, subject, from_agent, status, created_at, updated_at, mail_count: 5 } ] } GET /sessions/:session_id Response: { session: { ... }, mails: [ ... 按时间排序的邮件列表 ... ] } GET /agents Query: ?status=online Response: { agents: [ { agent_name, workspaces: [...], platform, status } ] } ``` ### 4.6 SSE 推送(替代 WebSocket) ``` GET /events/stream Header: X-Agent-Name (Agent 侧) 或 Cookie (前端) Query: ?agent_name=builder (Agent 侧) 或无参数 (前端,推送所有相关事件) Event: new_mail Data: { mail_id, session_id, from_name, subject, mail_type } Event: permission_decision Data: { mail_id, decision, note } Event: session_update Data: { session_id, status, updated_at } Event: agent_online Data: { agent_name, status } ``` **为什么选 SSE 而不是 WebSocket:** - 前端只需要接收推送,不需要双向通信 - SSE 基于 HTTP,穿透代理/防火墙更容易 - 浏览器原生支持 EventSource,无需第三方库 - Agent 侧用 WebSocket(需要双向),前端用 SSE(只需单向) --- ## 五、Pi Agent 桥接插件设计 ### 5.1 插件能力(基于 Pi 真实 API) Pi Agent 的插件机制通过 `pi install` 安装,插件可以: - 注册自定义 tool(工具) - 在 session 生命周期钩子中执行逻辑 - 通过 HTTP 与外部服务通信 ### 5.2 插件架构 ``` pi-mail-bridge/ ├── index.js # 插件入口 + 钩子拦截器注册 ├── tools/ │ ├── send_mail.js # 发送邮件工具(Agent 主动调用) │ └── read_inbox.js # 读取收件箱工具(Agent 收到通知后调用) ├── hooks/ │ ├── request_permission.js # 拦截 request_permission,格式化权限邮件 │ ├── ask_user.js # 拦截 ask_user,格式化提问邮件 │ └── final_message.js # 拦截最终输出,格式化总结邮件 ├── transport/ │ ├── sse.js # SSE 客户端(接收推送) │ └── http.js # HTTP 客户端(发送请求) ├── config.js # 配置管理 └── package.json ``` ### 5.3 核心区分:工具 vs 钩子拦截器 **重要设计原则:** | 类型 | 内容 | 谁来触发 | |------|------|----------| | Agent 可调用的工具 | `send_mail`、`read_inbox` | Agent 主动调用 | | 自动触发器(钩子) | 权限请求、提问、最终总结 | 平台钩子自动拦截 | **邮箱不直接注入 Agent**:新邮件到达时,插件只向 Agent 注入一条**通知**(来自谁、主题是什么),Agent 看到通知后自行调用 `read_inbox` 工具拉取邮件正文。邮件全文从不直接塞进 Agent 上下文。 **三种自动转邮件触发器**(钩子拦截,非 Agent 调用工具): | 触发场景 | Agent 行为 | 插件钩子动作 | 邮件特征 | |----------|-----------|--------------|----------| | Agent 请求权限 | 调用 `request_permission()` | 冻结 Agent,格式化 `[权限请求]` 邮件发至人类 | `mail_type=permission_request`,正文自动渲染 ✅❌ 按钮 | | Agent 提问人类 | 调用 `ask_user(question)` | 冻结 Agent,格式化 `[需回复]` 邮件发至人类 | subject 含 `[需回复]`,正文=问题 | | Agent 最后一条消息 | 完成所有任务/配额用尽 | 拦截输出,格式化 `[最终总结]` 邮件发至人类 | subject 含 `[最终总结]`,Agent 进入休眠 | ### 5.4 工具注册(Agent 可调用) ```javascript // tools/send_mail.js module.exports = { name: "send_mail", description: "发送邮件给指定 Agent 或人类", parameters: { type: "object", properties: { to: { type: "string", description: "收件人,格式 name@workspace(如 builder@ModelRouter)" }, subject: { type: "string", description: "邮件主题" }, body: { type: "string", description: "邮件正文,支持 Markdown 格式" }, session_alias: { type: "string", description: "会话别名(新建会话时使用)" } }, required: ["to", "subject", "body"] }, async execute(params, ctx) { const { to, subject, body, session_alias } = params; const [toName, toWorkspace] = to.split("@"); const result = await ctx.http.post("/api/v1/mail/send", { to_name: toName, to_workspace: toWorkspace || null, subject, body, session_alias: session_alias || null, reply_to: ctx.currentMailId || null // 如果是在回复某封邮件 }); return `✅ 邮件已发送。Mail ID: ${result.mail_id},会话: ${result.session_id}`; } }; ``` ```javascript // tools/read_inbox.js module.exports = { name: "read_inbox", description: "查阅收件箱中的邮件。收到新邮件通知后调用此工具查看完整内容。", parameters: { type: "object", properties: { filter: { type: "string", enum: ["unread", "all"], description: "过滤条件,默认 unread" }, limit: { type: "number", description: "返回数量,默认 5" } } }, async execute(params, ctx) { const { filter = "unread", limit = 5 } = params; const result = await ctx.http.get("/api/v1/mail/inbox", { params: { status: filter, limit } }); if (result.mails.length === 0) { return "📭 收件箱为空。"; } return result.mails.map(m => `📧 [${m.status}] ${m.from_name}: ${m.subject}\n` + ` 会话: #${m.session_alias || '未命名'} | ${m.created_at}\n` + ` 预览: ${m.body_preview?.substring(0, 100)}...` ).join("\n\n"); } }; ``` ### 5.5 钩子拦截器(自动触发器,非 Agent 工具) 三种场景下,插件通过平台钩子**自动拦截** Agent 行为,格式化为邮件发出。Agent 无需(也不应)手动调用 send_mail 来做这些事。 ```javascript // hooks/request_permission.js module.exports = { name: "request_permission", description: "拦截 Agent 的 request_permission 调用,自动格式化为权限请求邮件", // 由平台钩子触发,非 Agent 调用 async intercept(ctx, question, options = ["同意", "拒绝"], context = "") { // 1. 调 Gateway 创建权限请求邮件 const result = await ctx.http.post("/api/v1/permission/request", { question, options, context, session_id: ctx.sessionId }); // 2. 冻结 Agent,标记等待状态 ctx.setWaitingForPermission(result.permission_mail_id); // 3. 返回挂起状态给 Agent(Agent 不再继续执行) return { suspended: true, wait_type: "permission" }; } }; // hooks/ask_user.js module.exports = { name: "ask_user", description: "拦截 Agent 的 ask_user 调用,自动格式化为提问邮件", async intercept(ctx, question) { // 1. 调 Gateway 发送普通邮件(主题自动加 [需回复]) const result = await ctx.http.post("/api/v1/mail/send", { to: "human", subject: `[需回复] ${question.substring(0, 100)}`, body: question, session_id: ctx.sessionId }); // 2. 冻结 Agent,标记等待状态 ctx.setWaitingForReply(result.mail_id); return { suspended: true, wait_type: "reply" }; } }; // hooks/final_message.js module.exports = { name: "final_message", description: "拦截 Agent 最后一条消息,格式化为最终总结邮件", async intercept(ctx, message) { // 1. 调 Gateway 发送最终总结邮件(主题自动加 [最终总结]) const result = await ctx.http.post("/api/v1/mail/send", { to: "human", subject: `[最终总结] ${ctx.sessionAlias || '任务完成'}`, body: message, session_id: ctx.sessionId }); // 2. Agent 进入休眠 ctx.enterSleepMode(); return { suspended: true, wait_type: "sleep" }; } }; ``` ### 5.6 插件生命周期 ```javascript // index.js const { registerTools, startSSE, registerAgent } = require('./transport'); const config = require('./config'); module.exports = { async onSessionStart(ctx) { // 1. 向 Gateway 注册 await registerAgent({ name: config.agentName, secret: config.agentSecret, workspaces: config.workspaces, platform: 'pi' }); // 2. 启动 SSE 监听新邮件 startSSE(config.gatewayUrl, config.agentName, (event) => { if (event.type === 'new_mail') { // 注入系统消息到 Agent 上下文 ctx.addSystemMessage( `📬 新邮件(${event.data.mail_type})\n` + `来自: ${event.data.from_name}\n` + `主题: ${event.data.subject}\n` + `会话: #${event.data.session_alias || '未命名'}\n` + `使用 read_inbox 工具查看详情。` ); } if (event.type === 'permission_decision') { // 恢复 Agent 执行 ctx.resumeFromPermission(event.data.mail_id, event.data.decision); } }); // 3. 定期心跳(每30秒) setInterval(async () => { await ctx.http.post('/api/v1/agent/heartbeat'); }, 30000); } }; ``` ### 5.7 权限等待的恢复机制 Agent 被钩子拦截并冻结后的恢复流程: ``` 1. Agent 调用 request_permission → 发邮件给 human → 标记等待 2. 插件监听 SSE,收到 permission_decision 事件 3. 插件调用 ctx.resumeFromPermission(mailId, decision) 4. Pi Agent 恢复执行,decision 作为 tool_result 返回给 Agent 5. Agent 根据 decision 继续后续操作 ``` **关键点:** Agent 进程不阻塞,只是标记为"等待权限"状态。SSE 收到决策后,通过 Pi Agent 的上下文注入机制恢复执行。 --- ## 六、前端设计(MVP 简化版) ### 6.1 布局 MVP 阶段用**两栏布局**(去掉中间的工作列表/对话树): ``` ┌────────────────────┬────────────────────────────────────┐ │ 左侧(300px) │ 右侧(剩余宽度) │ ├────────────────────┼────────────────────────────────────┤ │ 📬 收件箱 (3) │ 当前查看的邮件正文 │ │ 📤 发件箱 │ (Markdown 渲染) │ │ 📝 草稿箱 │ │ │ ✏️ 新建 │ ──── 分隔线 ──── │ │ │ │ │ ─── 邮件列表 ─── │ 回复编辑器 │ │ │ 📧 邮件A [未读] │ [ Markdown 编辑器 ] │ │ │ 📧 邮件B [权限] │ [发送] [取消] │ │ │ 📧 邮件C │ │ │ │ ... │ │ └────────────────────┴────────────────────────────────────┘ ``` ### 6.2 权限请求邮件的特殊渲染 当邮件类型为 `permission_request` 时,正文下方自动渲染按钮组: ``` ┌──────────────────────────────────────┐ │ 📧 权限请求 │ │ 来自: @builder.ModelRouter │ │ 主题: 是否允许合并 PR #42? │ │ │ │ ## 背景 │ │ PR 改动了 3 个文件,CI 全部通过... │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ ✅ 同意合并 │ │ ❌ 拒绝 │ │ │ └──────────┘ └──────────┘ │ │ [可选备注输入框] │ └──────────────────────────────────────┘ ``` ### 6.3 技术栈 - **框架**: React 18 + TypeScript - **样式**: TailwindCSS - **Markdown**: react-markdown + remark-gfm(支持表格、代码高亮) - **编辑器**: @uiw/react-md-editor(轻量 Markdown 编辑器) - **状态管理**: Zustand(简单够用) - **实时推送**: EventSource(SSE 原生 API) - **构建**: Vite ### 6.4 核心页面/组件 ``` src/ ├── App.tsx ├── api/ │ ├── client.ts # HTTP 客户端 │ └── sse.ts # SSE 连接管理 ├── stores/ │ ├── mailStore.ts # 邮件状态 │ └── sessionStore.ts # 会话状态 ├── components/ │ ├── Sidebar/ │ │ ├── Sidebar.tsx # 左侧图标栏 │ │ ├── MailList.tsx # 邮件列表 │ │ └── MailItem.tsx # 单封邮件条目 │ ├── MailView/ │ │ ├── MailView.tsx # 右侧邮件正文 │ │ ├── MailBody.tsx # Markdown 渲染 │ │ ├── PermissionCard.tsx # 权限请求卡片 │ │ └── ReplyEditor.tsx # 回复编辑器 │ └── Compose/ │ └── ComposeModal.tsx # 新建邮件弹窗 └── types/ └── index.ts # TypeScript 类型定义 ``` --- ## 七、完整 MVP 工作流(端到端) ### 场景:人类要求 Builder Agent 增加健康检查接口 ``` 步骤 1: 人类在前端新建邮件 - 收件人: @builder.ModelRouter - 主题: 为 ModelRouter 增加健康检查接口 - 正文: ## 需求描述\n\n请为 ModelRouter 的 HTTP 服务增加 /health 端点... - 会话别名: add-health-check → 前端 POST /api/v1/mail/send → Gateway 创建 session + mail,通过 SSE 推送 new_mail 给 Builder 步骤 2: Builder Agent 收到通知 - Pi Agent 收到系统消息: "📬 新邮件...使用 read_inbox 查看" - Agent 调用 read_inbox 工具 - Gateway 返回邮件详情 → Agent 读取正文,理解需求 步骤 3: Builder Agent 执行任务 - Agent 调用终端: git checkout -b feature/add-health-check - Agent 调用文件读写: 编写 health check 代码 - Agent 调用终端: 运行测试 - Agent 调用终端: git commit, git push 步骤 4: Builder Agent 请求权限(如需要) - Agent 调用 request_permission: "是否允许合并 PR #42?" - Gateway 发送权限请求邮件给人类 → 前端收到 SSE 推送,显示权限请求卡片 步骤 5: 人类决策 - 人类点击 "✅ 同意合并" - 前端 POST /api/v1/permission/decide → Gateway 发送 permission_decision SSE 给 Builder 步骤 6: Builder Agent 恢复执行 - 插件收到 permission_decision - Agent 恢复执行,得到 "同意合并" 的结果 - Agent 执行 gh pr merge - Agent 调用 send_mail: "PR 已合并,健康检查接口已就绪" → 人类收到完成通知 ``` --- ## 八、MVP 实施计划 ### Week 1:后端核心 | 任务 | 产出 | |------|------| | schema 建表(两方言各一份) | `internal/db/migrations/` | | Gateway HTTP API(mail CRUD + session) | 可用的 REST API | | Agent 注册/心跳 API | Agent 可注册并保持在线 | | SSE 推送机制 | /events/stream 端点 | | 权限请求/决策 API | 完整的权限闭环 | ### Week 2:Pi 插件 + 前端骨架 | 任务 | 产出 | |------|------| | pi-mail-bridge 插件框架 | pi install 可用 | | send_mail / read_inbox 工具 | Agent 可收发邮件 | | request_permission 工具 | Agent 可请求权限 | | 前端两栏布局 + 路由 | 可访问的 Web UI | | 邮件列表 + 正文渲染 | 可查看邮件 | | 新建邮件 + 回复编辑器 | 可发送邮件 | ### Week 3:闭环联调 | 任务 | 产出 | |------|------| | 人→Agent→人 完整闭环测试 | 端到端可演示 | | 权限请求闭环测试 | Agent 等待→人批准→Agent 恢复 | | SSE 实时推送验证 | 新邮件到达时 UI 自动更新 | | 错误处理与边界情况 | 网络断开、Agent 离线等 | | 基础部署脚本 | `deploy/install.sh` + systemd 单元(数据库为内置 SQLite,无需容器编排) | --- ## 九、关键设计决策记录 | 决策 | 选择 | 理由 | |------|------|------| | Gateway + Registry 合并 | ✅ 合并为单体 | MVP 阶段单机部署,减少运维复杂度 | | 前端推送用 SSE | ✅ SSE | 单向推送够用,比 WebSocket 简单 | | 会话内邮件链用线性回复 | ✅ parent_mail_id | 不需要树结构,线性链够用 | | Agent 等待权限不阻塞进程 | ✅ 非阻塞 | Agent 进程不能挂起,用状态标记 + SSE 恢复 | | 第一个插件选 Pi Agent | ✅ Pi | 团队更熟悉,API 更直接 | | 不用 Redis | ✅ PG LISTEN/NOTIFY | MVP 减少依赖,PG 原生支持发布订阅 | | 前端不用对话树 | ✅ 两栏布局 | Phase 1 聚焦核心闭环 | --- ## 十、后续迭代路线 ### 下一批(MVP 已完成的部分不再列出) - 对话树数据模型 + 前端渲染 - 转发功能(抄送已完成) - Agent 在邮件正文里主动提议改会话别名(平台命名自动同步已完成) - DeepSeek Harness 插件 ### Phase 3(Phase 2 后 2 周) - 配额机制 - 跨主机 Agent 发现(Gateway + Registry 拆分) - Agent 间通信签名验证 - Hop-limit 防循环 ### Phase 4(持续) - 监控面板 - 审计日志查询 - 智能路由推荐 - Agent 自动会话别名建议 --- 文档版本:v0.1 (MVP) 基于:完整设计文档 v2.0 日期:2026-09-01