5fecb6d215
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>
350 lines
11 KiB
Markdown
350 lines
11 KiB
Markdown
# agent-notify 设计规格
|
||
|
||
**日期:** 2026-05-26
|
||
**状态:** 已批准(brainstorming)
|
||
**目标:** 在本地/远程 tmux 中运行 Cursor CLI 与 Claude Code 时,通过 Agent hook 触发 OSC 777 桌面通知,经 tmux 透传至 Ghostty 终端。
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
用户经常在本地 tmux 和远程 SSH tmux 中使用 AI Agent CLI(Cursor 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` hook(CLI 中不可靠)
|
||
|
||
---
|
||
|
||
## 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 | 直接写 stdout(Cursor)或返回 terminalSequence(Claude) |
|
||
| tmux 无 client_tty | 回退 DCS passthrough |
|
||
| config 中 event 关闭 | hook 静默 exit 0 |
|
||
| Claude stop_hook_active=true | 不发送通知,输出 `{}` |
|
||
| doctor 发现 allow-passthrough 未开 | 打印修复提示,不阻断 install |
|
||
| OSC 发送失败 | exit 1,stderr 输出原因(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 passthrough(linw1995)](https://www.linw1995.com/en/agent-native-system-notifications/)
|
||
- [soloterm/tnotify](https://github.com/soloterm/tnotify)
|