1e7efd9c3b
Second-phase brainstorming: centralized HTTP server, Web dashboard, and hook status reporting to replace deprecated local inbox. Co-authored-by: Cursor <cursoragent@cursor.com>
7.7 KiB
7.7 KiB
agent-notify 第二期:远端状态仪表盘 设计规格
日期: 2026-06-02
状态: 已批准(brainstorming)
目标: 各开发机 Hook 将 Agent 实时状态上报到自托管 HTTP Server,Web 页面按机器、工作目录、Agent 类型分类查看;详情含最后一次用户与 Agent 消息(各 ≤2000 字符)。
1. 背景与目标
第一期(v0.2 inbox)在本地聚合通知记录(inbox serve + SSH RemoteForward + CLI/TUI)。第二期改为中心化远端 Server + Web 仪表盘,满足:
- 实时状态(非纯事件流):每台机器、每个工作目录、每种 Agent 类型一行当前状态
- 上报包含 hostname + IP、工作目录
- 详情:从 hook 提供的 transcript 解析最后 user/assistant 各一条,单条最多 2000 字符
- Web 支持按机器 / 目录 / Agent / 状态 分类筛选
- 单机自托管,共享 Bearer Token;网络暴露由用户自行配置
- 弃用本地 inbox:Hook 默认只报远端;
inbox子命令标记 deprecated,代码下个大版本再删
成功标准
agent-notify server可启动 HTTP 服务(API + 内嵌 Web)- 各机器配置
[remote]后,Cursor/Claude hook 在发通知后上报状态,失败不影响 hook 退出码 - Web 展示所有机器的合并视图,默认按
updated_at降序;offline由 server 根据 5 分钟无更新判定 - 同一
hostname + ip + cwd + agent仅一条记录,多会话取最近活跃
非目标(第二期)
- 历史事件流、告警推送
- 多用户 RBAC、每机器独立 token
beforeSubmitPrompt实时更新last_user- 删除 inbox 源码(仅 deprecated)
- macOS/Windows server 部署文档(实现保持可交叉编译,优先 Linux 验证)
2. 方案选择
| 方案 | 描述 | 结论 |
|---|---|---|
| A | 同仓库 agent-notify server + SQLite + embed Web |
选用 |
| B | 独立 server 二进制 | 版本对齐成本高,YAGNI |
| C | 内存态 + JSON 快照 | 查询与持久化弱 |
3. 架构
┌─────────────────┐ hook ┌──────────────────┐
│ Cursor / Claude │ ────────────► │ agent-notify hook │
└─────────────────┘ └────────┬─────────┘
│ OSC 777(不变)
│ POST /api/v1/status
▼
┌──────────────────────┐
│ agent-notify server │
│ SQLite + HTTP API │
│ embed Web (轮询 3s) │
└──────────────────────┘
组件
internal/remote— 状态模型、session_key、HTTP 上报客户端internal/transcript— 从transcript_pathjsonl 解析最后 user/assistant,截断 2000internal/hostmeta—hostname+ 非 loopback IP 列表internal/server— SQLite store、Bearer 中间件、REST handlers、offline计算web/— 静态仪表盘(embed)- Hook 改动 —
recordInbox→reportRemote;[inbox]默认关闭并 deprecated
4. 数据模型
Session 键(合并规则 C)
session_key = SHA256(hostname + "\0" + primary_ip + "\0" + cwd + "\0" + agent)[:32] hex
primary_ip:上报ips中第一个 IPv4,无则空字符串- 同一键 upsert 覆盖,不保留历史行
conversation_id仅存字段,不拆行
状态枚举
| status | 含义 | 典型 hook |
|---|---|---|
running |
Agent 刚产出回复 | response |
waiting |
等待用户输入 | stop |
tool |
工具执行中 | tool |
idle |
长时间空闲 | idle |
offline |
5 分钟无更新 | server 计算 |
上报 JSON(POST /api/v1/status)
{
"hostname": "dev-box",
"ips": ["192.168.1.10"],
"agent": "Cursor",
"cwd": "/home/user/proj",
"status": "waiting",
"event": "stop",
"conversation_id": "uuid",
"last_user": "…",
"last_agent": "…",
"updated_at": "2026-06-02T12:00:00Z"
}
last_user/last_agent:可选;有 transcript 时更新,截断 2000;读失败时不覆盖已有字段(server merge:空字符串不覆盖非空列)updated_at:客户端 UTC;server 亦可写入received_at
SQLite 表 sessions
| 列 | 类型 | 说明 |
|---|---|---|
| session_key | TEXT PK | |
| hostname | TEXT | |
| ips | TEXT | JSON 数组 |
| agent | TEXT | |
| cwd | TEXT | |
| status | TEXT | |
| event | TEXT | 最近触发事件名 |
| conversation_id | TEXT | |
| last_user | TEXT | |
| last_agent | TEXT | |
| updated_at | TEXT | ISO8601 |
| received_at | TEXT | server 写入 |
索引:hostname, cwd, agent, updated_at。
5. API
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/v1/status |
Bearer | Upsert |
| GET | /api/v1/status |
Bearer | 列表;query: host, cwd, agent, status |
| GET | /api/v1/meta |
Bearer | distinct hosts / cwds / agents |
| GET | /healthz |
无 | 健康检查 |
| GET | / |
无 | Web UI |
- Token:server
--token或AGENT_NOTIFY_TOKEN(启动必填) - 401:缺失或错误 token
6. Hook 与 Transcript
流程
hook → 通知(不变)→ build report → 解析 transcript(可选)→ POST remote
- 失败仅
logx.Append,返回码仍为 0 - 替换
recordInbox;cfg.Remote.Enabled为 false 时跳过上报
事件 → status
| event | status |
|---|---|
| stop | waiting |
| response | running |
| tool | tool |
| idle | idle |
Transcript
- Cursor stop payload:
transcript_path→ 读 jsonl 尾部窗口(最大 256KB)→ 最后 user/assistant - Claude:有 path 则同逻辑;无则跳过文本
- 字段映射:支持
role+content或 Cursor transcript 常见行格式(实现时以实测样本为准)
机器标识
hostname:os.Hostname()ips:网卡非 loopback IPv4(可含 IPv6),去重排序
7. Web UI
- 内嵌静态页,
GET /api/v1/status每 3s 轮询 - 侧栏筛选:机器(hostname + ip)、Agent、状态;工作目录可侧栏或下拉
- 主表列:机器 | 目录(basename,完整路径 title)| Agent | 状态 | 更新时间 | 详情展开(last_user / last_agent)
- Token:首次输入存
sessionStorage,请求带Authorization offline行样式变灰(由 API 返回status=offline)
8. 配置
[remote]
enabled = true
url = "http://your-server:8080"
token = "shared-secret"
timeout_ms = 2000
[inbox]
enabled = false # deprecated
install默认写入[remote]占位与[inbox] enabled = falsedoctor检查remote.url、remote.token非空(enabled 时)
命令
agent-notify server --listen :8080 --db ./agent-notify.db --token "$AGENT_NOTIFY_TOKEN"
inbox 子命令保留,执行时打印 deprecation 警告。
9. 错误处理与测试
| 场景 | 行为 |
|---|---|
| 远端不可达 | log,通知照常 |
| Token 错误 | 401 |
| Transcript 过大 | 只读尾部 256KB |
| 同键多次上报 | upsert |
测试:session_key 稳定、截断 2000、Bearer、offline 5min、hook 失败不影响 RunCursor/RunClaude。
10. 迁移说明
- 在自托管机启动
agent-notify server - 各开发机
config.toml配置[remote] - 停止依赖
inbox serve/ SSH RemoteForward(可选保留至下版本删除)