# 架构 ## 进程模型:CLI 子命令 + 常驻 Host 整个程序是**单个 exe `notify.exe`**,靠命令行第一个参数分流成两类角色: | 角色 | 触发方式 | 是否加载 Avalonia | 生命周期 | |------|----------|------------------|----------| | **CLI 子命令** | `notify save\|notify\|input\|cleanup` | 否(纯互操作 / 落盘) | 即起即退(~100ms) | | **Host** | `notify` 或 `notify host` | 是 | 常驻(单例,无主窗口) | 这样设计的原因:hook 在你每次发消息时都会被拉起,必须**极快返回、绝不阻塞 Claude Code**;而真正画 UI 的 Avalonia 较重,放在一个**只初始化一次**的常驻进程里。 ```mermaid flowchart TD M["Main(args)"] --> SW{"args[0]"} SW -->|save| CSAVE[CliRunner.Save] SW -->|notify| CNOTIFY[CliRunner.Notify] SW -->|input| CINPUT[CliRunner.Input] SW -->|cleanup| CCLEAN[CliRunner.Cleanup] SW -->|其它 / host| RUN[RunHost:单例互斥量 + Avalonia] CSAVE -. 不加载 Avalonia .-> X1[退出] CNOTIFY -. 不加载 Avalonia .-> X2[退出] RUN --> APP[App:托盘 + SpoolWatcher + ToastManager] ``` - **单例**:`RunHost` 用命名互斥量 `ClaudeCodeNotifyHost` 保证只有一个 Host;第二个实例直接退出。 - **保活**:`ShutdownMode.OnExplicitShutdown`,没有主窗口也不退,只有托盘"退出"才结束。 ## 两条数据通道 1. **状态文件**(每会话)`%TEMP%\claude-notify-{session_id}.json` - `save` 写入:前台窗口句柄、prompt、WT 标签 RuntimeId、调用方 exe 路径。 - `notify`/`input` 读取,拼成通知;`cleanup` 删除。 2. **spool 队列** `%TEMP%\claude-notify-spool\*.json` - `notify`/`input` 把一条 `NotifyMessage` 原子落盘,Host 用 `FileSystemWatcher` 消费。 - 取代命名管道,**让 CLI 写完即走、不等 Host**(见 README 的「非阻塞投递」时序)。 ## 组件 / 目录 ``` Notify/ ├── Program.cs 入口:子命令分流 + Host 单例 ├── App.axaml(.cs) Avalonia 应用:托盘、SpoolWatcher、收到消息弹 toast ├── Cli/ │ ├── HookInput.cs stdin JSON 的 DTO(源生成反序列化) │ └── CliRunner.cs save/notify/input/cleanup 实现 + 消息清洗 ├── Ipc/ │ ├── NotifyMessage.cs 投递给 Host 的弹窗请求 DTO │ ├── NotificationSpool.cs 落盘投递 + 拉起 Host(客户端)/ 消费(Host) │ ├── SpoolWatcher.cs Host 侧目录监视 │ └── IpcConstants.cs 互斥量名等 ├── Models/ │ ├── ToastSettings.cs 持久化设置 + 枚举(HEdge/VEdge) │ ├── ToastRequest.cs Host 内部的一次弹窗请求 │ └── StateData.cs 每会话状态 ├── Services/ │ ├── SettingsService.cs 设置读写 │ ├── StateStore.cs 状态文件读写 │ └── ToastManager.cs toast 创建 / 堆叠定位 / 排队 ├── ViewModels/ ToastViewModel、SettingsViewModel(partial 源生成属性) ├── Views/ ToastWindow、SettingsWindow ├── Interop/ 原生互操作(见 interop.md) └── Serialization/ └── AppJsonContext.cs System.Text.Json 源生成上下文 ``` ## 线程模型 - CLI 子命令在单线程跑完即退。 - Host 里:`FileSystemWatcher` 回调在线程池线程触发 → `App.OnNotify` 里先做一次"是否前台"判断和提示音,再 `Dispatcher.UIThread.Post` 切回 UI 线程创建 toast。 - 所有 Avalonia 对象操作都在 UI 线程;GDI / COM 互操作可在任意线程,但本项目主要在 UI 线程调用。