Files
agent-dashboard/docs/superpowers/specs/2026-05-26-agent-notify-design.md
laily 5fecb6d215 feat: implement agent-notify CLI with hooks and tmux passthrough
Add OSC 777 notification sender, Cursor/Claude hook adapters,
config/install commands, and README for Ghostty + tmux setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 11:19:34 +08:00

350 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# agent-notify 设计规格
**日期:** 2026-05-26
**状态:** 已批准(brainstorming
**目标:** 在本地/远程 tmux 中运行 Cursor CLI 与 Claude Code 时,通过 Agent hook 触发 OSC 777 桌面通知,经 tmux 透传至 Ghostty 终端。
---
## 1. 背景与目标
用户经常在本地 tmux 和远程 SSH tmux 中使用 AI Agent CLICursor CLI、Claude Code)。当 Agent 完成一轮回复、回到等待输入状态时,用户希望收到桌面通知,而无需一直盯着终端。
Ghostty 终端支持 OSC 777(带标题/正文的桌面通知)和 OSC 9。tmux 默认会吞掉 OSC 序列,需通过 DCS passthrough 或写入 client TTY 透传。大部分 Agent CLI 支持 hook,可在 hook 中触发通知。
### 成功标准
- Agent 完成一轮回复、等待输入时,Ghostty 弹出桌面通知(默认开启)
- 通知标题包含 Agent 名称(Cursor / Claude)和项目目录或 tmux 窗口名
- 同一套 CLI 在以下场景均可工作:
- 本地 Ghostty → 本地 tmux → Agent
- 本地 Ghostty → SSH → 远程 tmux → Agent
- 本地 Ghostty → 本地 tmux → SSH → 远程 tmux → Agent(嵌套 tmux
- 提供 `agent-notify install` 一键写入 Cursor 与 Claude Code 的 hook 配置
- 触发事件可配置:stop(默认开)、idle(默认关)、tool(默认关)
### 非目标(首版)
- notify-send 等系统通知回退
- macOS / Windows 支持
- 不支持 Cursor IDE,仅支持 Cursor CLI`cursor-agent`
- Cursor CLI 的 `afterAgentResponse` hookCLI 中不可靠)
---
## 2. 方案选择
在 brainstorming 中评估了三种方案:
| 方案 | 描述 | 结论 |
|------|------|------|
| A | 自研 `agent-notify` CLI + 安装脚本 | **选用** |
| B | 包装 soloterm/tnotify | 外部依赖,Claude terminalSequence 适配不内聚 |
| C | 纯 Shell 脚本 | 嵌套 tmux 逻辑难维护 |
---
## 3. 整体架构
```
┌─────────────┐ hook 触发 ┌──────────────────┐
│ Cursor CLI │ ────────────────► │ │
│ Claude Code │ ────────────────► │ agent-notify │
└─────────────┘ stdin/env/flag │ (核心 CLI) │
└────────┬─────────┘
│ OSC 777
┌──────────────────────────┐
│ tmux 透传层 (0~N 层) │
└────────────┬─────────────┘
│ SSH (远程场景)
┌─────────────┐
│ Ghostty │
└─────────────┘
```
### 组件
1. **agent-notify CLI**Go 单二进制)
- `send`:发送通知(供 hook 或直接调用)
- `hook`Agent 专用入口,解析 stdin JSON
- `install`:写入 hook 配置与默认 config
- `test`:发送测试通知
- `doctor`:检查 Ghostty/tmux/allow-passthrough 配置
2. **Hook 适配层**
- Cursor`~/.cursor/hooks.json`
- Claude Code`~/.claude/settings.json`
3. **配置文件**
- `~/.config/agent-notify/config.toml`
---
## 4. 通知协议
### OSC 格式
Ghostty 优先使用 **OSC 777**(支持标题 + 正文):
```
\033]777;notify;{title};{body}\007
```
OSC 9 作为备选(仅正文):
```
\033]9;{body}\007
```
首版默认使用 OSC 777。
### tmux 透传策略
发送优先级:
1. **写 client TTY**(单层 tmux 最可靠)
- `tmux display-message -p '#{client_tty}'`
- 将 OSC 序列写入该 TTY
2. **DCS passthrough**(嵌套 tmux 必需)
- 每层 tmux 包裹:`\033Ptmux;\033{inner}\033\\`
- 嵌套 N 层则包裹 N 次
3. **直写 stdout**(无 tmux 且 hook 允许时)
### tmux 前置配置
用户需在涉及的每一层 tmux 中启用(安装脚本检测并提示):
```tmux
set -g allow-passthrough on # 需要 tmux 3.2+
```
### 远程 SSH 说明
- 远程 tmux 中写入 client TTY 时,数据经 SSH pty 传回本地
- 若本地还有 tmux,本地 tmux 也需 `allow-passthrough on`,否则 OSC 在本地被吞掉
- 嵌套 tmux(本地 tmux → SSH → 远程 tmux)需双层 passthrough 或双层 DCS 包裹
---
## 5. Hook 接入
### 触发事件映射
| 事件 | 含义 | Cursor CLI hook | Claude Code hook | 默认 |
|------|------|-----------------|------------------|------|
| stop | Agent 完成回复,等待输入 | `stop` | `Stop` | 开 |
| idle | 长时间无输入(约 60s | 无等价 hook | `Notification` | 关 |
| tool | shell/工具执行结束 | `afterShellExecution` | `PostToolUse`shell 类) | 关 |
### 通知内容
- **标题:** `{agent} — {context}`
- `{agent}``Cursor``Claude`
- `{context}`tmux 窗口名(`#{window_name}`);若无 tmux 则用 `basename(cwd)`
- **正文:** 按事件类型
- stop`等待输入`
- idle`空闲 60s+,等待输入`
- tool`工具执行完成`
模板可在 config.toml 中覆盖。
### Cursor CLI 集成
配置文件:`~/.cursor/hooks.json`(全局)或项目级 `.cursor/hooks.json`
```json
{
"version": 1,
"hooks": {
"stop": [
{ "command": "agent-notify hook cursor stop" }
],
"afterShellExecution": [
{ "command": "agent-notify hook cursor tool" }
]
}
}
```
- `stop` hook 在 CLI 中可用
- hook 进程的 stdout 可能被捕获,CLI 内部通过写 TTY / DCS passthrough 发送 OSC,不依赖 stdout
- `afterShellExecution` 仅在 config 中 `events.tool = true` 时由 install 写入
### Claude Code 集成
配置文件:`~/.claude/settings.json`
Claude Code v2.1.139+ 的 hook 进程无 controlling TTY**不可**直接写 `/dev/tty`。须通过 JSON 返回 `terminalSequence`,由 Claude Code 代为写入终端:
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "agent-notify hook claude stop"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "agent-notify hook claude idle"
}
]
}
]
}
}
```
`agent-notify hook claude *` 输出:
```json
{"terminalSequence": "\033]777;notify;Cursor — myproject;等待输入\007"}
```
- `Stop` hook 必须检查 `stop_hook_active`:若为 true 则输出空 JSON 并 exit 0,避免无限循环
- `terminalSequence` 中的 OSC 序列由 Claude Code 写入其终端路径,天然兼容 tmux
---
## 6. CLI 接口
### 命令
```
agent-notify send [--title T] [--body B] [--event stop|idle|tool]
agent-notify hook cursor stop|tool
agent-notify hook claude stop|idle
agent-notify install [--cursor] [--claude] [--all] [--force]
agent-notify test
agent-notify doctor
```
### 环境变量(可选覆盖)
| 变量 | 含义 |
|------|------|
| `AGENT_NOTIFY_AGENT` | Agent 名称 |
| `AGENT_NOTIFY_CWD` | 工作目录 |
| `AGENT_NOTIFY_EVENT` | 事件类型 |
### 配置文件
路径:`~/.config/agent-notify/config.toml`
```toml
[events]
stop = true
idle = false
tool = false
[notify]
protocol = "osc777" # osc777 | osc9
title_template = "{agent} — {context}"
body_stop = "等待输入"
body_idle = "空闲 60s+,等待输入"
body_tool = "工具执行完成"
```
`hook` 子命令读取 config,若对应 event 为 false 则静默 exit 0。
---
## 7. 安装流程
`agent-notify install --all` 执行:
1. 检测 `agent-notify` 是否在 PATH
2. 运行 `doctor`:检查是否在 tmux、tmux 版本、`allow-passthrough` 状态
3. 写入 `~/.config/agent-notify/config.toml`(不存在时)
4. 合并写入 Cursor `~/.cursor/hooks.json`(不覆盖已有同 event hook,除非 `--force`
5. 合并写入 Claude `~/.claude/settings.json`
6. 运行 `agent-notify test` 验证通知
---
## 8. 错误处理
| 场景 | 行为 |
|------|------|
| 不在 tmux | 直接写 stdoutCursor)或返回 terminalSequenceClaude |
| tmux 无 client_tty | 回退 DCS passthrough |
| config 中 event 关闭 | hook 静默 exit 0 |
| Claude stop_hook_active=true | 不发送通知,输出 `{}` |
| doctor 发现 allow-passthrough 未开 | 打印修复提示,不阻断 install |
| OSC 发送失败 | exit 1stderr 输出原因(hook 不应阻断 Agent |
Cursor/Claude hook 脚本始终以 exit 0 结束(Claude Stop 除外需遵循 stop_hook_active 规则),避免影响 Agent 正常运行。
---
## 9. 技术选型
- **语言:** Go 1.22+
- **依赖:** 标准库为主;TOML 解析可用 `github.com/BurntSushi/toml`
- **分发:** `go install github.com/.../agent-notify@latest` 或仓库内 `make install`
- **平台:** Linux + Ghostty(首版)
---
## 10. 测试计划
### 单元测试
- OSC 777/9 序列生成
- tmux 层数检测与 DCS 多层包裹
- config 解析与 event 开关
- Claude hook JSON 输出格式
### 集成测试(手动)
| 场景 | 命令 | 期望 |
|------|------|------|
| 无 tmux | `agent-notify test` | Ghostty 弹出通知 |
| 本地 tmux | 在 tmux 内 `agent-notify test` | Ghostty 弹出通知 |
| 远程 tmux | SSH 到远程 tmux 内 test | 本地 Ghostty 弹出通知 |
| 嵌套 tmux | 本地 tmux → SSH → 远程 tmux test | 本地 Ghostty 弹出通知 |
| Cursor stop | cursor-agent 完成一轮 | 通知标题含 Cursor + 项目名 |
| Claude Stop | claude 完成一轮 | 通知标题含 Claude + 上下文 |
---
## 11. 项目结构(预期)
```
agent-notify/
├── cmd/agent-notify/main.go
├── internal/
│ ├── notify/ # OSC 生成与发送
│ ├── tmux/ # 层数检测、passthrough、client_tty
│ ├── hook/ # cursor/claude stdin 解析
│ ├── config/ # TOML 配置
│ └── install/ # hook 配置合并写入
├── docs/superpowers/specs/
│ └── 2026-05-26-agent-notify-design.md
├── go.mod
├── Makefile
└── README.md
```
---
## 12. 参考资料
- [Ghostty OSC 实现](https://github.com/ghostty-org/ghostty/blob/main/src/terminal/osc.zig)
- [Claude Code Hooks - terminalSequence](https://code.claude.com/docs/en/hooks)
- [Cursor Hooks 文档](https://cursor.com/docs/hooks)
- [tmux OSC passthroughlinw1995](https://www.linw1995.com/en/agent-native-system-notifications/)
- [soloterm/tnotify](https://github.com/soloterm/tnotify)