Merge upstream and add generic Codex notifications

This commit is contained in:
2026-08-04 15:16:36 +08:00
31 changed files with 374 additions and 218 deletions
+4 -4
View File
@@ -9,7 +9,7 @@
| **CLI 子命令** | `notify save\|notify\|input\|cleanup` | 否(纯互操作 / 落盘) | 即起即退(~100ms |
| **Host** | `notify``notify host` | 是 | 常驻(单例,无主窗口) |
这样设计的原因:hook 在你每次发消息时都会被拉起,必须**极快返回、绝不阻塞 Claude Code**;而真正画 UI 的 Avalonia 较重,放在一个**只初始化一次**的常驻进程里
hook 需要快速返回。CLI 只写入状态和通知队列,Avalonia UI 由常驻进程加载一次
```mermaid
flowchart TD
@@ -25,15 +25,15 @@ flowchart TD
RUN --> APP[App:托盘 + SpoolWatcher + ToastManager]
```
- **单例**`RunHost` 用命名互斥量 `ClaudeCodeNotifyHost` 保证只有一个 Host;第二个实例直接退出。
- **单例**`RunHost` 用命名互斥量 `NotifyHost` 保证只有一个 Host;第二个实例直接退出。
- **保活**`ShutdownMode.OnExplicitShutdown`,没有主窗口也不退,只有托盘"退出"才结束。
## 两条数据通道
1. **状态文件**(每会话)`%TEMP%\claude-notify-{session_id}.json`
1. **状态文件**(每会话)`%TEMP%\notify-{session_id}.json`
- `save` 写入:前台窗口句柄、prompt、WT 标签 RuntimeId、调用方 exe 路径。
- `notify`/`input` 读取,拼成通知;`cleanup` 删除。
2. **spool 队列** `%TEMP%\claude-notify-spool\*.json`
2. **spool 队列** `%TEMP%\notify-spool\*.json`
- `notify`/`input` 把一条 `NotifyMessage` 原子落盘,Host 用 `FileSystemWatcher` 消费。
- 取代命名管道,**让 CLI 写完即走、不等 Host**(见 README 的「非阻塞投递」时序)。
+8 -7
View File
@@ -1,13 +1,14 @@
# 构建与安装
## 安装(用户)
## Codex 接入示例
```bash
claude plugin marketplace add https://git.pchuan.top/cc-tools/notify.git
claude plugin install claude-code-notify@claude-code-notify
在用户级 `~/.codex/config.toml` 中加入:
```toml
notify = ["E:\\notify\\scripts\\notify.cmd", "codex"]
```
重启 Claude Code 后生效。插件的 `hooks/hooks.json` 指向 `${CLAUDE_PLUGIN_ROOT}/scripts/notify.cmd`,首次触发钩子时该脚本会从 Release 下载单文件 `notify.exe``bin/`,之后常驻
`notify` 必须放在用户级配置中,项目目录内的 `.codex/config.toml` 不会覆盖它
引导脚本(`scripts/notify.cmd``scripts/notify.sh`)顶部的 `DOWNLOAD_URL` 决定从哪拉取 exe;下载用临时文件 + 原子改名 + mkdir 锁,并发触发不会重复下载。
@@ -30,7 +31,7 @@ NativeAOT 静态链接 Skia / HarfBuzz / ANGLE,产出**单个无依赖 exe**
```bash
# 从 "Developer Command Prompt for VS" 运行,或用脚本(自动用 vswhere 配 vcvars
scripts\build.bat
# 产物:bin\notify.exe(单文件,~40MB
# 产物:bin\notify.exe未压缩单文件,40 MB
```
然后把它作为 Release 资产发布,并确保引导脚本的 `DOWNLOAD_URL` 指向它:
@@ -41,7 +42,7 @@ gh release create v0.1.0 bin/notify.exe
> AOT 配置在 `Notify/Notify.csproj``PublishAot` 条件块 + `CoreUtils.*.Static` 静态库包 + 发布后清理)。源生成 COM / UIAutomation 与静态渲染需真机运行验证,详见 [interop.md](interop.md)。
## 不接 Claude 的手动烟雾测试
## 手动烟雾测试
```bash
notify host & # 起 Host(托盘出现)
+18 -8
View File
@@ -1,20 +1,30 @@
# Hook 与 CLI
## Hook → 子命令映射
## Codex notify
| Claude Code 事件 | 子命令 | 作用 |
|------------------|--------|------|
在用户级 `~/.codex/config.toml` 中配置:
```toml
notify = ["E:\\notify\\scripts\\notify.cmd", "codex"]
```
Codex 调用 `notify codex <JSON>`。程序处理 `agent-turn-complete` 事件,使用 `thread-id``turn-id` 标识通知,以 `last-assistant-message` 作为正文。
## Hook 与子命令映射
| 事件 | 子命令 | 作用 |
|------|--------|------|
| `UserPromptSubmit` | `notify save` | 记录前台窗口、prompt、WT 标签、调用方图标路径 |
| `Stop` | `notify notify` | 弹"任务完成"通知(自动消失,聚焦时更短) |
| `Notification` | `notify input` | 弹"需要输入"通知(常驻),按类型分标题 |
| `PreToolUse``AskUserQuestion`/`ExitPlanMode` | `notify input` | 提问 / 出 Plan 时弹常驻通知 |
| `SessionEnd` | `notify cleanup` | 删除该会话状态文件 |
`hooks/hooks.json`(插件形式)里命令为 `${CLAUDE_PLUGIN_ROOT}/bin/notify.exe <子命令>`;直连 `settings.json` 时可写 `notify <子命令>` 或绝对路径
客户端可调用 `notify.exe <子命令> [来源]`。来源为 `codex` 时固定显示 Codex 图标
## stdin JSON
Claude Code 通过 **stdin** 把事件数据以 JSON 传入。`HookInput` 关心这几个字段:
外部 hook 通过 **stdin** 把事件数据以 JSON 传入。`HookInput` 使用以下字段:
| 字段 | 用途 |
|------|------|
@@ -30,10 +40,10 @@ Claude Code 通过 **stdin** 把事件数据以 JSON 传入。`HookInput` 关心
| 条件 | 标题 |
|------|------|
| `tool_name == AskUserQuestion` | Claude is Asking |
| `tool_name == AskUserQuestion` | Input Required |
| `tool_name == ExitPlanMode` | Plan Ready for Approval |
| `notification_type == permission_prompt` | Permission Required |
| `notification_type == idle_prompt` | Claude is Waiting |
| `notification_type == idle_prompt` | Application is Waiting |
| `notification_type == elicitation_dialog` | MCP Asks |
| 其它 | Input Required |
@@ -41,7 +51,7 @@ Claude Code 通过 **stdin** 把事件数据以 JSON 传入。`HookInput` 关心
## 状态文件
路径:`%TEMP%\claude-notify-{session_id}.json``session_id` 做了文件名安全过滤)。
路径:`%TEMP%\notify-{session_id}.json``session_id` 做了文件名安全过滤)。
```jsonc
{
+1 -1
View File
@@ -30,7 +30,7 @@ Windows 限制后台进程抢焦点。组合技:还原最小化 → 模拟一
## 取图标(AppIcon
`ExtractIconEx` 拿 HICON → `GetIconInfo` 取彩色位图 → `GetDIBits` 以 32bpp 自上而下读出 BGRA → 构造 `Avalonia.Media.Imaging.Bitmap`。老图标无 alpha(全 0)时补成不透明,避免整块透明。取不到则回退默认 Claude 图标
`ExtractIconEx` 拿 HICON → `GetIconInfo` 取彩色位图 → `GetDIBits` 以 32bpp 自上而下读出 BGRA → 构造 `Avalonia.Media.Imaging.Bitmap`。老图标无 alpha(全 0)时补成不透明。取不到图标时隐藏图标区域
## AOT