Gemini CLI 终端通知实战:配置与原理全解析(OSC 9 / OSC 777 / 终端铃声回退机制)
Gemini CLI 内置了一套实验性的系统通知能力:当会话结束、或 Agent 暂停等待你批准工具调用时,它可以通过终端转义序列触发操作系统级通知,让你可以放心切到别的窗口。本文以 docs/cli/notifications.md 为主线,完整覆盖启用方式、事件类型与终端兼容性要求,并深入 terminalNotifications.ts 与 useRunEventNotifications.ts,讲清“何时发、发给谁、发什么、发到哪个终端协议”这一整条链路。
功能定位与适用场景
Gemini CLI 可以发送系统通知来提醒你两类时机:
- 会话成功完成(session complete);
- 需要你的介入(action required),例如等待你批准一次工具调用、回答 Agent 的提问。
通知在两类场景下收益最大:运行耗时较长的自动化任务,以及使用 Plan Mode 让 Agent 在后台持续工作时——你只需瞥一眼桌面通知即可判断是否该回到终端。
注意:这是一个实验性功能,仍处于活跃开发阶段,默认关闭,需要手动在
/settings中启用(详见下文)。
终端要求:OSC 9、OSC 777 与 BEL 回退
文档明确说明,CLI 使用 OSC 9(\x1b]9;...BEL)终端转义序列触发系统通知,iTerm2、WezTerm、Ghostty、Kitty 等现代终端均支持该序列;当终端不支持 OSC 9 时,Gemini CLI 回退为终端铃声(BEL,\x07),多数终端会把它表现为任务栏闪烁或系统提示音。
从源码 terminalNotifications.ts 可以看到,除了 OSC 9 与 BEL,实现中还提供了一种更通用的 OSC 777(\x1b]777;notify;标题;正文BEL,XDG 桌面通知协议,被 GNOME Terminal、Konsole、VTE 系终端广泛支持)。四种方法被统一定义在 TerminalNotificationMethod 枚举中:
| 方法 | 转义序列 | 说明 |
|---|---|---|
auto(默认) |
按终端自动选择 | 见下方自动选择规则 |
osc9 |
\x1b]9;标题 | 副标题 | 正文\x07 |
iTerm2、WezTerm、Ghostty、Kitty 等 |
osc777 |
\x1b]777;notify;标题;正文\x07 |
GNOME/Konsole 等 XDG 桌面通知终端 |
bell |
\x07 |
通用回退,终端任务栏闪烁或响铃 |
auto 模式下的选择逻辑在 notifyViaTerminal 中,依据 TerminalCapabilityManager 探测到的终端能力:
- 检测到 iTerm2 → 发送 OSC 9;
- 检测到 Alacritty、Apple Terminal、VS Code 集成终端、Windows Terminal → 发送 BEL(这些终端没有可靠的桌面通知路径,铃声/任务栏闪烁更稳妥);
- 其余终端 → 发送 OSC 777。
此外,如果你在 tmux 或 GNU screen 里运行,转义序列会被包进 passthrough 透传封装,防止被多层终端吞掉(wrapWithPassthrough):
- tmux:
\x1bPtmux;(内部 ESC 双写为 \x1b\x1b)...\x1b\ - screen:
\x1bP...\x1b\
这解释了为什么在 tmux 会话中通知依然能到达最外层终端——对应的行为在 terminalNotifications.test.ts 中被显式验证。
启用通知:/settings 对话框或 settings.json
通知默认关闭。启用有两种等价方式。
方式一:交互式 /settings 对话框
- 在交互会话中输入
/settings打开设置对话框; - 进入 General 分类;
- 将 Enable Notifications 开关切换为 On。
方式二:直接编辑 settings.json
{
"general": {
"enableNotifications": true
}
}
源码中的配置定义
这两个设置在 settingsSchema.ts 中有完整定义,可以据此核对取值范围与默认值:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
general.enableNotifications |
boolean | false |
标签为 "Enable Terminal Notifications",控制 action-required 提示与会话完成两类运行事件通知;requiresRestart: false,即修改后无需重启会话即可生效 |
general.notificationMethod |
enum | "auto" |
取值 auto / osc9 / osc777 / bell,对应上表四种发送方式;同样无需重启 |
配置读取入口是 isNotificationsEnabled:只有当合并后的设置 general.enableNotifications === true 时通知才真正开启(严格等值判断,"true" 字符串等不会生效)。getNotificationMethod 则解析 notificationMethod,未知取值一律回落到 auto。
因此如果你的终端是 WezTerm/Kitty/Ghostty 这类原生支持 OSC 9 的终端,但自动探测结果不理想,可以直接显式指定:
{
"general": {
"enableNotifications": true,
"notificationMethod": "osc9"
}
}
测试用例 explicit osc9 场景 验证了显式方法可以覆盖自动探测(例如在 Windows Terminal 中强制发送 OSC 9)。
通知事件类型详解
文档列出两类通知事件,源码给出了各自的精确触发条件与文案模板。
1. Action required(需要处理)
当模型在等待用户输入或工具批准时触发。触发判定集中在 pendingAttentionNotification.ts 的 getPendingAttentionNotification,它按优先级扫描六种等待状态,任何一种处于挂起状态都会构造一条 attention 事件:
- 工具确认(
tool_confirmation):若挂起工具是ask_user,副标题为 “Answer requested by agent”,正文取第一个问题的题干;否则副标题为 “Approval required”,正文为 “Approve tool action: …”; - 命令确认(
command_confirmation):某条命令正在等待确认; - 认证确认(
auth_consent):认证流程等待确认; - 文件系统权限确认(
filesystem_permission_confirmation):只读路径访问等待确认; - 扩展更新确认(
extension_update_confirmation); - 循环检测确认(
loop_detection_confirmation)。
最终渲染出的系统通知文案由 buildRunEventNotificationContent 组装:标题固定为 “Gemini CLI needs your attention”,副标题默认 “Action required”,正文默认 “Open Gemini CLI to continue.”。
防打扰逻辑(useRunEventNotifications.ts)保证了通知不会刷屏:
- 焦点抑制:若终端当前持有焦点(且确实收到过焦点事件),则抑制通知——你既然在看屏幕,就不必再弹一次;
- 状态变化驱动:仅在“刚进入等待状态”“终端刚失去焦点”或“等待项内容变化(key 改变)”时发送;
- 冷却时间:同一等待项在 20 秒(
ATTENTION_NOTIFICATION_COOLDOWN_MS = 20_000)内不重复发送; - 自动清除:等待状态解除后,冷却记录立即重置。
2. Session complete(会话完成)
会话成功结束时触发。从 useRunEventNotifications.ts 看,触发条件是三者的精确交集:
- 流式状态发生
Responding → Idle的迁移(即一轮回复刚刚完成); - 终端不处于“有焦点”状态(焦点抑制同上);
- 当前没有挂起的 action-required 项——否则会改发 attention 通知,避免“完成”与“请处理”两条通知同时出现。
文案模板为:标题 “Gemini CLI session complete”,副标题 “Run finished”,正文默认 “The session finished successfully.”(实际调用时传入 “Gemini CLI finished responding.”)。
这个设计与 Plan Mode 或长任务自动化天然契合:后台跑一个多步骤任务,结束瞬间收到一次通知,且不会与“需要批准”类通知互相干扰。
通知内容的清洗与安全边界
通知正文最终要写进终端转义序列,源码对内容做了严格约束(terminalNotifications.ts):
| 字段 | 上限 | 默认回退 |
|---|---|---|
| title | 48 字符(MAX_NOTIFICATION_TITLE_CHARS) |
截断后为空则用 “Gemini CLI” |
| subtitle | 64 字符(MAX_NOTIFICATION_SUBTITLE_CHARS) |
为空则省略 |
| body | 180 字符(MAX_NOTIFICATION_BODY_CHARS) |
为空则用 “Open Gemini CLI for details.” |
具体处理包括:
- 转义序列剥离:正文中的 ANSI 颜色/控制序列与换行会被
sanitizeForDisplay移除,测试 strips terminal control sequences 验证了\x1b[32m与\n不会泄漏进 OSC 负载; - OSC 777 分号转义:OSC 777 协议本身以
;分隔字段,因此标题与正文中的;会被替换为:,避免截断序列(emitOsc777Notification,对应测试见 semicolon 用例); - 写入失败静默降级:
notifyViaTerminal捕获写入异常并仅记一条 debug 日志返回false,通知失败绝不影响主会话运行。
完整调用链与验证
整条链路在代码中的走向是:
- AppContainer.tsx 启动时通过
isNotificationsEnabled(settings)与getNotificationMethod(settings)读取配置,连同焦点状态、流式状态、各类挂起确认请求一起传入useRunEventNotificationsHook; - Hook 内按上文规则判定“是否需要发”;
- 命中后调用
notifyViaTerminal(notificationsEnabled, content, method),按方法选择 OSC 9 / OSC 777 / BEL 序列,必要时加 tmux/screen passthrough 封装,经writeToStdout写向终端; - 终端把序列交给操作系统/桌面环境呈现为系统通知。
可运行与可验证的依据集中在:
- 单元测试 terminalNotifications.test.ts:覆盖“关闭时不写输出”“iTerm2 走 OSC 9 且以
\x07结尾”“Windows Terminal/Alacritty/VS Code 走 BEL”“未知终端走 OSC 777”“tmux/screen 透传封装”等 15+ 个断言; - UI 层测试 AppContainer.test.tsx:在多种挂起确认场景下断言
notifyViaTerminal被调用; - 集成入口测试 gemini.test.tsx:验证通知模块的接线与关闭时的静默行为。
推荐配置与使用建议
结合文档与源码行为,给出几条实用配置:
{
"general": {
"enableNotifications": true,
"notificationMethod": "auto"
}
}
- 在 iTerm2 / WezTerm / Ghostty / Kitty 中可保持
auto(前两者会被探测并选择 OSC 9 / OSC 777 路径);若想强制走 OSC 9 桌面通知,显式设置"notificationMethod": "osc9"; - 在 tmux / GNU screen 中使用时无需额外配置,passthrough 封装会自动处理;
- 通知只在你不看终端时才弹出(焦点抑制),所以测试时请切到别的窗口再触发等待批准的操作;
- 配合 Plan Mode 或长任务运行时开启,完成即收到 “session complete” 通知;
- 更多体验定制可参考 settings 文档,其中
enableNotifications与notificationMethod均标记为无需重启(requiresRestart: false),改完立即生效。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00