chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
85
docs/PLAN.md
85
docs/PLAN.md
@ -22,12 +22,12 @@ Phase 6 (Week 6+) 进阶功能 — 对话树 / 转发 / 配额 / DSH
|
||||
|
||||
### 1.1 项目初始化 ✅
|
||||
|
||||
- [x] 创建项目目录 `gateway/`
|
||||
- [x] 创建项目目录 `server/`
|
||||
- [x] `go mod init github.com/agentmail/gateway`
|
||||
- [x] 目录结构规划:
|
||||
|
||||
```
|
||||
gateway/
|
||||
server/
|
||||
├── cmd/
|
||||
│ └── server/
|
||||
│ └── main.go # 入口,启动 HTTP 服务
|
||||
@ -213,7 +213,7 @@ GET /api/v1/events/status → SSE 连接状态
|
||||
- [x] 两份 schema:`migrations/init.sql`(PG)与 `migrations/init_sqlite.sql`
|
||||
- [x] `DATABASE_URL` 识别 `postgres://`、`sqlite://`、`file:`、裸 `.db` 路径;空值 = 内置 SQLite
|
||||
- [x] SQLite 连接开 `WAL` + `busy_timeout=5000` + `foreign_keys=ON`,连接池限 1(单写者)
|
||||
- [x] 删除 `docker-compose.yml` 与重复的 `gateway/migrations/`
|
||||
- [x] 删除 `docker-compose.yml` 与重复的 `server/migrations/`
|
||||
|
||||
### 1.9 部署:systemd 单元
|
||||
|
||||
@ -305,12 +305,12 @@ Agent 不需要手动调工具。opencode 的插件契约没有提供拦截这
|
||||
|
||||
### 3.1 项目初始化
|
||||
|
||||
- [x] `web/` 目录,Vite + React + TypeScript + TailwindCSS
|
||||
- [x] `client/electron/` 目录,Vite + React + TypeScript + TailwindCSS
|
||||
|
||||
### 3.2 目录结构(已落地)
|
||||
|
||||
```
|
||||
web/src/
|
||||
client/electron/src/
|
||||
├── api/ client.ts(HTTP)/ sse.ts(EventSource + 指数退避重连)
|
||||
├── stores/ mail / session / contact / ui / auth(Zustand)
|
||||
├── components/ Sidebar / MailList / MailView / ComposePage / ContactPanel
|
||||
@ -743,7 +743,7 @@ execute: {
|
||||
缺头回 `Missing X-Agent-Name or X-Agent-Secret header`
|
||||
- [x] 畸形三维地址:缺 name 位、空 to、别名不存在均有明确错误文案
|
||||
- [x] 邮件内容 XSS 防护:react-markdown 默认不解析 raw HTML 且清空非 http(s)/mailto 协议 URL,
|
||||
新增回归测试 `web/test/markdown-xss.test.mjs`(`npm test`)守住这两个前提,
|
||||
新增回归测试 `client/electron/test/markdown-xss.test.mjs`(`npm test`)守住这两个前提,
|
||||
防止日后为「支持 HTML 邮件」加上 rehype-raw 而无声开口子
|
||||
- [x] 并发安全:`sse.Manager` 用 `sync.RWMutex`;`go build -race` 通过;
|
||||
实测 10 客户端并发连接(断开后计数归零)+ 20 封并发发信全部成功(SQLite WAL + busy_timeout)
|
||||
@ -1018,7 +1018,7 @@ execute: {
|
||||
**只有这两个端点接受 query 令牌**,其余一律 401 —— URL 里的令牌会进访问日志与 Referer;
|
||||
附件下载因此单独挂在 `UserAuthAllowQueryToken` 中间件下
|
||||
- [x] CORS 暴露 `Content-Disposition` 与 `Content-Length`(前者是取文件名所必需)
|
||||
- [x] `web/src/api/config.ts` 集中基地址与令牌:
|
||||
- [x] `client/electron/src/api/config.ts` 集中基地址与令牌:
|
||||
`VITE_API_BASE` 构建期注入、`window.__AGENTMAIL_API_BASE__` 运行时覆盖、`setToken()` 切凭证。
|
||||
业务代码不感知 Cookie 与密钥的差异,`src/api/` 可整体抽成 SDK
|
||||
- [x] `docs/API.md`:完整接口清单、三类调用者的认证边界、错误码约定
|
||||
@ -1343,7 +1343,7 @@ MVP 计划(Phase 1-6)已全部落地并在 systemd 部署态实测通过。
|
||||
只滑走覆盖层的话下次进列表又会弹回来
|
||||
- [x] `narrowPane` 在宽屏下**也维护**:否则从窄屏拖宽再拖回来,
|
||||
用户会发现自己回到了列表,刚打开的邮件不见了
|
||||
- [x] `web/test/narrow-layout.test.mjs`:结构性断言(现 28 条),
|
||||
- [x] `client/electron/test/narrow-layout.test.mjs`:结构性断言(现 28 条),
|
||||
钉住「覆盖而非分栏」「延迟卸载」「双层 rAF」「条件渲染而非 md:hidden」
|
||||
「无裸 px-6」等不变量。不做像素级视觉快照 —— 字体差异下极脆
|
||||
|
||||
@ -1388,7 +1388,7 @@ MVP 计划(Phase 1-6)已全部落地并在 systemd 部署态实测通过。
|
||||
空间不足时自动退化为 0,于是矮屏变成正常的顶对齐可滚布局,
|
||||
而高屏仍然垂直居中(390x844 与 1280x800 实测 centered=true)
|
||||
|
||||
验证:`web/test/manual/`(`npm run test:narrow` / `test:wide`)
|
||||
验证:`client/electron/test/manual/`(`npm run test:narrow` / `test:wide`)
|
||||
窄屏 18 项 + 宽屏 5 项全通过;结构性断言从 20 条扩到 37 条,
|
||||
把每一条修复都钉住。
|
||||
|
||||
@ -1589,7 +1589,7 @@ plan/workspace/full **一一对应** —— 不是巧合,是同一个问题的
|
||||
|
||||
- 前端有 Markdown XSS 与窄屏结构性回归,但没有**渲染组件跑断言**的测试
|
||||
- 深色主题未做
|
||||
- 窄屏实测脚本已入库(`web/test/manual/`),但没进 CI ——
|
||||
- 窄屏实测脚本已入库(`client/electron/test/manual/`),但没进 CI ——
|
||||
要一个 headless 环境加一个测试用 Gateway 实例
|
||||
- 登录限速与新建会话限速已改为 DB 事务(rate_limits 表),多实例部署不再各自计数
|
||||
- SQLite 抄送查询走 `json_each` 全表展开,无索引;单机量级下够用,
|
||||
@ -1680,6 +1680,67 @@ $ sqlite3 "SELECT COUNT(*) FROM attachments WHERE mail_id='2a64fdc8…'"
|
||||
- [ ] C:`GET /mail/{id}` 返回 `from_human` / `to_human`;`addr-verify` 两项转绿
|
||||
- [ ] D:契约文档有 `B-5.6` 条款;demo 按 `from_human` 决定是否回信
|
||||
|
||||
### 7.14 DSH 主动询问邮件桥接(严重阻塞缺口)
|
||||
|
||||
本轮生产问题:DSH 邮件会话调用 `ask_user_question` 后一直停在询问状态,AgentMail
|
||||
里没有任何待处理邮件。根因不是 SSE 或 Gateway 丢信,而是桥只监听了
|
||||
`approval/request`(危险工具的审批 seam),没有接管 `ask_user_question` 使用的
|
||||
`ctx.userQuestions` seam。DSH Web UI 是后者唯一 provider;邮件驱动会话没有人在 DSH
|
||||
页面作答,所以工具 Promise 永远不返回。
|
||||
|
||||
修复边界:
|
||||
|
||||
1. 在 DSH 的 `tools/execute` around-dispatch 中只拦截**邮件驱动会话**的
|
||||
`ask_user_question`,不替换全局 `userQuestions` provider(该服务只允许一个 provider,
|
||||
强行注册会与 Web UI 冲突)。
|
||||
2. 每个问题按原始 `id/question/options/multi_select` 生成一封 Gateway 待处理邮件;
|
||||
单选保留选项原文,自由文本与多选用备注输入承载。多问题按顺序询问,全部回答后
|
||||
还原 DSH 要求的 `{answers:[{id,selected,custom?}]}` 工具结果。
|
||||
3. Gateway 的权限请求端点增加 `kind=question`:复用现有决策人追溯、幂等 relay_key、
|
||||
待办列表与 SSE 回传,但**不套权限档位判定**(plan/full 也可能需要补充信息)。
|
||||
`kind=permission` 保持原行为。
|
||||
4. 待决映射必须在发 HTTP 请求前登记,堵住“人快速作答、SSE 先于 map 写入”的竞态;
|
||||
abort、插件卸载、永久 HTTP 失败均 fail closed,不留下悬挂 Promise。
|
||||
5. 前端把询问类待办显示为“等待回答”,自由文本/多选回答未填写时禁止提交;仍复用
|
||||
现有待处理入口,避免再造第二套不可见队列。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 单选询问经邮件选择后,DSH 工具拿到原始选项标签并继续运行
|
||||
- [ ] 无选项询问要求填写文本,空回答不能提交
|
||||
- [ ] 多选询问可提交多个原始标签,未知标签不被伪造为有效选择
|
||||
- [ ] 两个问题按顺序往返,最终答案数组保持原始 question id 与顺序
|
||||
- [ ] plan/workspace/full 三档中的主动询问均可送达;危险工具审批仍只在 workspace 产生
|
||||
- [ ] 非邮件驱动 DSH 会话继续使用 Web UI provider,不受桥影响
|
||||
|
||||
### 7.13 桥进程异常上报与可靠重试
|
||||
|
||||
本轮起因:桥进程意外终止只留在 journal,用户邮箱没有任何提示;同时“自动重连”
|
||||
与“处理中任务重试”被混为一谈。现状其实已有两层:systemd 对进程做指数退避重启,
|
||||
四桥 SSE 断线后自动重连;但它们不能回答“刚才正在处理的那封邮件怎么办”。
|
||||
|
||||
按三层修复,职责不能混:
|
||||
|
||||
1. **进程层由 systemd 自愈**:保留 `Restart=` + `RestartSteps=6` +
|
||||
`RestartMaxDelaySec=5min`。插件不在进程内部造第二个 supervisor。
|
||||
2. **异常退出必须发邮件**:共用 `deploy/service-failure-notify.mjs` 挂到
|
||||
`ExecStopPost`,读取 systemd 的 `SERVICE_RESULT/EXIT_CODE/EXIT_STATUS`;正常停止不报,
|
||||
exit-code/signal/oom-kill/timeout/watchdog 才报。Gateway 暂不可达时落本地 spool,
|
||||
下次 `ExecStartPost --flush` 补发。通知用稳定 `relay_key` 幂等,避免即时发送与补发重复。
|
||||
3. **处理中任务必须重试**:pi worker 无 `done` 就按 1s/2s 有界重投,最多 3 次;
|
||||
`done(ok=false)` 属于已经给出明确失败结论,不盲目重跑。重试耗尽后给原发件人一封
|
||||
故障邮件。整进程重启后仍由未读补拉兜底;homeagent 已有落盘 ledger,保留现状。
|
||||
4. **网络层继续重连**:SSE 3–5 秒重连、心跳下一周期再试,不把短暂网络抖动升级成
|
||||
进程重启。
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] notifier dry-run 能区分正常停止与异常退出,敏感密钥不进入正文/日志
|
||||
- [ ] Gateway 暂不可达时报告落盘,`--flush` 后只发一次
|
||||
- [ ] pi worker 首次异常退出后自动重投,同一会话仍串行
|
||||
- [ ] 连续三次异常后停止重试并调用失败回报,不形成无限崩溃循环
|
||||
- [ ] 四个桥宿主服务保留指数退避重启;正常 `systemctl restart` 不产生误报警邮件
|
||||
|
||||
---
|
||||
|
||||
## 文件清单(完整)
|
||||
@ -1689,7 +1750,7 @@ agentmail/
|
||||
├── docs/
|
||||
│ ├── MVP-SPEC.md # MVP 技术规格书
|
||||
│ └── PLAN.md # 本文件
|
||||
├── gateway/
|
||||
├── server/
|
||||
│ ├── cmd/server/main.go # 入口 + 路由编排
|
||||
│ ├── internal/
|
||||
│ │ ├── config/config.go
|
||||
@ -1730,7 +1791,7 @@ agentmail/
|
||||
│ └── opencode-mail-bridge/ # 第一个接入平台
|
||||
│ ├── package.json
|
||||
│ └── index.js # 凭证 + HTTP + SSE + 四个工具 + event 钩子
|
||||
├── web/
|
||||
├── client/electron/
|
||||
│ ├── src/
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── client.ts
|
||||
|
||||
Reference in New Issue
Block a user