# AGENTS.md
Windows Gotify Agent —— 在 Windows 10/11 上以托盘程序方式运行,通过 WebSocket 连接 Gotify Server,把新消息以 Windows 原生通知呈现。
## 用户场景
V1 核心场景(用户明确要求):
- Windows 接收 Gotify 通知(WebSocket → Windows 原生通知)
- 应用内查看历史消息(打开 MainWindow → 历史列表,自动拉最近 N 条 + 实时追加新消息)
- 列表条目支持双击跳转:消息 `extras` 里有 `client::notification.click.url` 时,默认浏览器打开
V1 暂不做(避免范围蔓延):多 Gotify Server、消息过滤、GUI 完整配置编辑、MSI/MSIX 安装器、Linux/macOS 端。
## Setup commands
- 环境:Windows 10 19041+ / Windows 11、Visual Studio 2022、.NET 8 SDK、Desktop development with .NET 工作负载
- 还原依赖: `dotnet restore`
- 编译: `dotnet build -c Release`
- 本地运行: `dotnet run -c Release`
- 自包含发布(推荐): `dotnet publish -c Release -r win-x64 --self-contained true`
- 发布产物: `bin\Release\net8.0-windows\win-x64\publish\`(整个目录一起发布,不要只复制 EXE)
## Project layout
- `WindowsGotify.csproj` — SDK 风格项目文件,net8.0-windows / x64,UseWPF + UseWindowsForms,WindowsPackageType=None(unpackaged)
- `app.manifest` — asInvoker 兼容性清单(Win10/11 supportedOS)
- `App.xaml` + `App.xaml.cs` — 应用入口、Mutex 单实例锁、AppNotificationManager 注册/注销、生命周期编排
- `MainWindow.xaml` + `MainWindow.xaml.cs` — 设置窗(Gotify URL、Client Token、保存并重连、测试通知);关闭按钮只 Hide 不退出
- `Models/` — `AppConfig`(嵌套 Gotify/Notification/Reconnect/History/Startup 五个子配置) + `GotifyMessage`(+ `GotifyMessagePage`、`PagingInfo`)
- `Services/ConfigService.cs` — `config.json` 读写,路径 `%LOCALAPPDATA%\WindowsGotify\config.json`
- `Services/Logger.cs` — 文本日志写入 `app.log`,线程安全
- `Services/StateService.cs` — `state.json` 持久化 `LastMessageId`,断线重连/历史同步的去重锚点
- `Services/GotifyApiService.cs` — `GET /message?limit=&since=` 拉历史补偿;`GetRecentMessagesAsync` 拉最近 N 条(供 UI 历史查看)
- `Services/GotifyWebSocketService.cs` — `/stream?token=...` WebSocket 循环、自动重连(指数退避 2→5→10→20→30→60)、History 补偿;新增 `MessageReceived` 事件供 UI 实时追加
- `Services/NotificationService.cs` — 通过 `AppNotificationBuilder` 弹 Windows 原生通知,Priority ≥ 8 走 Alarm 音;若消息 `extras` 含 `client::notification.click.url`,通过 `AddArgument("url=...")` 透传给 `OnNotificationInvoked`
- `Services/NotificationService.cs` — 通过 `AppNotificationBuilder` 弹 Windows 原生通知,Priority ≥ 8 走 Alarm 音
- `Utils/StartupManager.cs` — 写入 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` 实现当前用户开机启动
- `Tray/TrayIconService.cs` — WinForms `NotifyIcon` 托盘(打开设置/测试通知/重新连接/退出)
## Code style
- C# 12 / .NET 8,`enable` + `enable`
- 顶层 `sealed class`,文件作用域命名空间,主命名空间 `WindowsGotify.*`
- 异步 API:WebSocket、API、状态保存一律 `async/await`,UI 事件用 `async void` + try/catch
- `using System.*;` 优先,然后第三方(`Microsoft.Windows.AppNotifications.*` / `Microsoft.Win32`),最后 `WindowsGotify.*`
- WPF XAML 资源引用走 `x:Name`,代码隐藏用 `_camelCase` 私有字段
- WebSocket 资源用 `using` 或 `try/finally` 保证释放,`ClientWebSocket.Abort()` 用于强制重连
- 不要把 `Application Token` 写进此项目(只允许 `Client Token`)
## Runtime data
- 配置文件: `%LOCALAPPDATA%\WindowsGotify\config.json`
- 状态文件: `%LOCALAPPDATA%\WindowsGotify\state.json`
- 运行日志: `%LOCALAPPDATA%\WindowsGotify\logs\app.log`
## Testing instructions
- V1 不带自动化测试,依赖手动 + Gotify 端到端验证
- 单元自测: 托盘菜单 → 「测试通知」,应弹出标题为「Gotify 测试」的 Windows 通知
- 端到端发消息(PowerShell):
```powershell
$headers = @{ "X-Gotify-Key" = "" }
$body = @{ title = "Hadoop"; message = "NameNode 启动完成"; priority = 3 } | ConvertTo-Json
Invoke-RestMethod -Uri "https:///message" -Method Post -Headers $headers -ContentType "application/json" -Body $body
```
- 反向代理(Nginx / NPM)必须透传 WebSocket Upgrade 头,详见方案文档第 14 节
- 任意代码改动后必须执行: `dotnet restore && dotnet build -c Release`,并在目标 Windows 10/11 上跑一次端到端
- **已知 WindowsAppSDK 2.4.0 self-contained 打包 bug**:`dotnet build` 出来的 `bin\...\win-x64\` 缺 `Microsoft.WindowsAppRuntime.Insights.Resource.dll`,首次启动 `AppNotificationManager.Register()` 会报 `0x8007007E 找不到指定的模块`。
- 修法 A(dev):从已装 WindowsAppSDK 的程序(如 `C:\Program Files\WSL\wslsettings\`)拷贝 5 个 DLL 到 build 目录:
- `Microsoft.WindowsAppRuntime.Bootstrap.dll`、`Microsoft.WindowsAppRuntime.Bootstrap.Net.dll`、`Microsoft.WindowsAppRuntime.dll`、`Microsoft.WindowsAppRuntime.Insights.Resource.dll`、`Microsoft.WindowsAppRuntime.pri`
- 修法 B(prod):`dotnet publish` 后同样需要补这 5 个文件,或者目标机器装 WindowsAppRuntime system-wide(`winget install Microsoft.WindowsAppRuntime.2`)
- `/stream` 是 WebSocket 端点,`/message?limit=&since=` 是历史补偿端点,二者都只接受 `Client Token`
- 消息去重锚点 = `state.LastMessageId`,只处理 `Id > LastMessageId` 的消息,启动时用 `since=LastMessageId` 拉增量
- HTTP `ClientToken` 走 `Authorization: Bearer `;WebSocket 走 query `?token=`(日志中要打码为 `token=***`)
- 声音策略: `priority >= 8` → `AppNotificationSoundEvent.Alarm`,否则 `Default`
## PR & commit conventions
- 分支策略: 默认分支 `main`,所有改动走 feature 分支 + PR
- 提交信息: 简体中文 conventional commits,前缀使用 `feat:` / `fix:` / `docs:` / `refactor:` / `chore:` / `build:`
- 改动 `.csproj` / `Microsoft.WindowsAppSDK` 版本号必须在 PR 描述中单独说明,不允许顺手升级
- 涉及 `Client Token` / 真实 Gotify URL 的示例值用占位符 `` / ``,禁止提交真实凭据
## Security
- `Client Token` 只能进入 `config.json`,日志中只允许 `token=***` 打码输出
- 公网部署必须使用 HTTPS + wss;ws:// / http:// 仅允许内网
- 通知点击仅允许打开经 Gotify `extras` 传入的 URL,不要任意打开消息文本里出现的链接
- 启动项走 `HKCU\...\Run`(当前用户)而非 `HKLM`,避免提权与全员副作用
- 发布前在干净 Windows 10/11 上验证通知注册、通知中心授权、WebSocket 反代、自包含目录依赖完整