feat: Claude Code 原生 Windows 通知(C# / .NET 10 + Avalonia 12)

为 Claude Code 提供原生 Windows toast 通知:点击跳回原窗口、切回 Windows
Terminal 标签、跨虚拟桌面、调用方图标、非阻塞投递;NativeAOT 单文件分发。
This commit is contained in:
2026-06-22 18:05:15 +08:00
commit 5ce2c8a982
53 changed files with 3889 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
# Hook 与 CLI
## Hook → 子命令映射
| Claude Code 事件 | 子命令 | 作用 |
|------------------|--------|------|
| `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 <子命令>` 或绝对路径。
## stdin JSON
Claude Code 通过 **stdin** 把事件数据以 JSON 传入。`HookInput` 关心这几个字段:
| 字段 | 用途 |
|------|------|
| `session_id` | 状态文件隔离;为空则忽略本次 |
| `prompt` | UserPromptSubmit 的用户输入,用作"完成"通知正文 |
| `notification_type` | Notification 类型:`permission_prompt` / `idle_prompt` / `elicitation_dialog` / … |
| `message` | Notification / 提问的文本 |
| `tool_name` | PreToolUse 工具名:`AskUserQuestion` / `ExitPlanMode` |
> **注意**stdin 用 `OpenStandardInput()` 读**原始字节**再按 **UTF-8** 解码。不能用 `Console.In`——WinExe 下它不可靠,且会用控制台代码页(中文系统是 GBK)把中文解成乱码。
## 标题分流(input
| 条件 | 标题 |
|------|------|
| `tool_name == AskUserQuestion` | Claude is Asking |
| `tool_name == ExitPlanMode` | Plan Ready for Approval |
| `notification_type == permission_prompt` | Permission Required |
| `notification_type == idle_prompt` | Claude is Waiting |
| `notification_type == elicitation_dialog` | MCP Asks |
| 其它 | Input Required |
被**过滤**(不弹)的类型:`auth_success` / `elicitation_complete` / `elicitation_response`
## 状态文件
路径:`%TEMP%\claude-notify-{session_id}.json``session_id` 做了文件名安全过滤)。
```jsonc
{
"Hwnd": 329712, // 触发时前台窗口句柄
"Prompt": "重构通知模块", // 完成通知正文
"WtRuntimeId": "42.288...",// WT 当前标签 RuntimeId(非 WT 为空)
"CallerExePath": "...\\WindowsTerminal.exe" // 调用方 App,用于取图标
}
```
- `notify`/`input` 读它来填 `NotifyMessage`(含点击要激活的 `TargetHwnd`、要切的标签、要显示的图标)。
- `cleanup` 删它。若 `SessionEnd` 没触发(崩溃等),文件会残留在 `%TEMP%`,无害。
## 消息清洗
`notify`/`input` 投递前会把正文里的换行 / 制表 / 多余空格折叠成单行,避免撑乱 toast 布局;超长部分由 toast 的两行省略号截断。