# 通知内容与传输原理

[返回 README](../README.md)

## 两种通知，而不是成功/失败报告

| 标题 | Pi 事件 | 含义 |
| --- | --- | --- |
| Pi 等待输入 | `agent_settled` | 不再有自动 retry、compaction retry 或排队的 continuation，等待下一步输入；不保证任务成功 |
| Pi 需要你的操作 | `ui_prompt_start` | 扩展的 select、confirm、input、editor 或 custom UI 正在等待你 |

`agent_end` 不发通知，因为后面可能仍有自动执行。`ui_prompt_end` 使尚未发送的弹窗通知过期；若先前有被弹窗推迟的 settled 通知，会重新检查是否可以发送，而不是另发一条“弹窗已关闭”。

settled 按执行轮次、按通道记录发送尝试。排队、查询 tmux 位置或检查透传权限时还不算发送；若此时弹窗打开，关闭后只恢复尚未尝试的通道。已经写出 BEL、调用终端写入或发起 HTTP 的通道不会重复尝试，包括失败或超时的情况。明确关闭或被透传策略跳过的通道也不重试。新 run 或会话结束会使旧轮次失效。

Pi 将重叠/嵌套 UI 合并为一个外层等待区间。插件按区间和执行轮次去重，不按标题或时间窗去重，所以连续同标题的问题仍能分别通知。立即关闭的短弹窗可能不通知，以免收到过期提醒。

不是所有 Pi 界面都触发这些事件：启动信任界面、内置选择器和绕过 `ctx.ui` 的第三方界面不保证覆盖。第三方扩展升级后的实际交互需要重新验收。

## 默认字段

```text
Pi 等待输入

项目：pi-notify
主机：dev-server
位置：work / 窗口 2 / 分屏 1
```

| 字段 | 来源 | 用途 |
| --- | --- | --- |
| 项目 | 工作目录的最后一级名称 | 哪个项目需要处理 |
| 主机 | 运行 Pi 的 hostname | 应回到哪台远程机器 |
| work | tmux session 名称 | 应进入哪个 tmux 会话 |
| 窗口 2 | tmux window index | 状态栏中的窗口编号 |
| 分屏 1 | tmux pane index | 窗口里的分屏编号，可用 tmux 的显示分屏编号功能辨认 |

窗口和分屏索引不是内部 ID，也不保证从 0 或 1 开始：取决于你的 tmux 配置。它们是派发时查询到的快照；到达时可能已重命名或重排。tmux 外省略位置行，查询失败时显示“未知会话 / 窗口 未知”，不会拿难懂的内部 ID 代替。

默认不发送完整路径、窗口名称、prompt title、用户回答、对话、工具输出或内部 ID。但项目名、主机名、tmux 会话名本身也可能敏感。

## 可选详细信息

设置 `content.details: true`，会追加：

```text
tmux ID：session $1 / window @7 / pane %12
Pi 会话 ID：abcdef123456
```

- `$1`：tmux 的 session ID。
- `@7`：tmux 的 window ID，**不是窗口索引 7**。
- `%12`：tmux 的 pane ID，**不是第 12 个分屏**。
- `abcdef123456`：截短的 Pi session ID，可用于区分同一个项目的多个 Pi 会话。

这些标识符用于定位和诊断，不是认证凭据。详细信息和 `promptTitle`、完整路径是独立开关；开启详细信息不会自动泄露提示内容或完整路径。配置见 [configuration.md](configuration.md)。

## OSC 到底是什么？

OSC（Operating System Command）是终端控制序列的一类。名字中有 “Operating System”，但这里**不是远程 Linux 执行本地操作系统命令**。

OSC 777 的 `notify` 约定携带标题和正文。本地终端识别它后，向本地操作系统请求桌面通知：

```text
Pi（本地或远程）
  │
  ├─ 普通 TTY：直接写出 OSC 777
  └─ tmux pane：用 DCS 包装后由 tmux 透传
          │
          │ 远程运行时经 SSH 连接传输
          ▼
本地 WezTerm / Ghostty
  │ 识别 notify，调用本地系统通知 API
  ▼
本地桌面通知
```

tmux 不是 terminal 通知的前提，只负责窗口 BEL 标记以及 tmux 内的 OSC 透传。直接在本地终端或 SSH 远程 shell 的交互式 TTY 中运行 Pi 时，OSC 会直接写到当前终端；本地运行时没有 SSH 这一层。

OSC 通道依赖 TUI/TTY、终端连接、本地终端支持和桌面权限。SSH 断开、tmux 客户端未附着或系统勿扰时，不能保证显示。多个 attached client 可能分别收到一份；只要 Pi 进程仍在运行，需要脱离终端连接发送时可使用 webhook。

### 控制序列中的符号

以下都是可读记号，**不是让你复制执行的 shell 命令**：

```text
ESC ] 777 ; notify ; 标题 ; 正文 ST
```

- `ESC` 是字节 `0x1b`。
- `777;notify` 表示该通知协议及操作。
- 分号分隔字段，所以用户内容中的分号会替换为逗号。
- `ST` 是结束符，编码为 `ESC` 后接反斜线。
- 字段中的控制字符会被清洗，按 UTF-8 字节数截断，不会切坏多字节字符。

tmux 中再包一层 DCS（Device Control String）透传包装：外层以 `ESC P tmux;` 开始、以 `ST` 结束；内部每个 ESC 加倍。该包装告诉 tmux 将 OSC 交给外面的终端，而不是当作普通 pane 内容处理。

OSC 字段中的换行会合并为空格，所以桌面正文通常是连续文本；webhook 把相同标题和正文渲染到用户配置的 JSON 模板中，字符串里的换行会由 JSON 正确转义。不同 webhook 服务最终如何展示由服务自身决定。

## 三条互相独立的通道

```text
                 ┌─ BEL → tmux bell flag（窗口提醒）
Pi 等待人工操作 ─┼─ OSC 777 → 本地终端 → 桌面通知
                 └─ HTTP JSON → webhook 服务
```

- **BEL** 是单个 `0x07` 字节，不包含项目名、标题或正文，也不包 tmux passthrough。它让 tmux 根据用户设置标记或响铃。
- **OSC** 携带可读通知内容；写出成功不代表操作系统已经显示。
- **webhook** 向完整 URL 发送用户模板生成的 JSON POST，不依赖 SSH/终端连接。HTTP 2xx 也不代表下游客户端已经展示。

每事件每通道最多一次尝试，不自动重试；超时可能发生在服务端已经接收之后，不能保证 exactly-once。通道独立排队，一个失败不会阻止其他通道。[限制与诊断 →](troubleshooting.md)
