RFC-001:Beacon 技术方案¶
| 状态 | Draft(待评审) |
| 日期 | 2026-10-07 |
| 上游 | 任务书(定位 / 分期 / 验收标准) · 产品与技术实施总方案(2026-10-11 定版;冲突处已拍板,见 mvp-issues 拍板记录) |
| 下游 | Solution 项目目录 · MVP Issue 列表 |
| 平台 | Windows 10 1809 (17763)+ / Windows 11,仅 Windows(TargetPlatformMinVersion=10.0.17763.0) |
1. 背景与定位(摘要)¶
Beacon 是常驻 Windows 桌面的开发者状态与操作中心(Developer Status & Action Center),核心是 Signal + Action:替开发者盯着开发环境(GitHub / CI / 机器 / Agent),在需要注意时主动发出信号,并提供一键操作。它不是 Web Dashboard,不替代 GitHub / Jenkins / Grafana,只做聚合 + 提醒 + 低成本下一步操作。
产品定位、场景、分期与 MVP 验收标准以任务书为准,本 RFC 不重复,只落实技术决策。
2. 目标与非目标¶
2.1 MVP 目标¶
- 开机自启、常驻(Tray + 桌面悬浮层),不打扰。
- GitHub(PR / Actions)状态聚合为统一 Severity,桌面可见。
- 失败发生时主动 Toast;点击即达失败详情。
- 一键 Run / Retry / Cancel / Open,全程不开浏览器。
- Local First:配置在本机,Secrets 进系统安全存储。
2.2 非目标(MVP 明确不做)¶
复杂插件市场、Workflow DSL/引擎、AI Agent、Jenkins/Docker 全功能管理、数据库、云账号、用户系统、团队协作、云同步、复杂图表、复杂主题。Beacon 连接工具,不重建工具。
3. 总体架构¶
单进程 WinUI 3 应用,四层职责分离:
┌──────────────────── Beacon.App(WinUI 3 Shell,唯一 exe)────────────────────┐
│ Tray │ Hotkey │ PinnedHost(L0/L1) │ QuickPanel(L2) │ Detail(L3) │ Settings │
│ Toast │ 启动项 │ 单实例 │ 多显示器/DPI │
└──────────────┬───────────────────────────────────────────────┬──────────────┘
│ Event Bus(UI 只消费状态,不直接拉数据;代码建 UI + code-behind)│
┌──────────────▼──────────── Beacon.Core ───────────────────────▼──────────────┐
│ Models(Severity/LifecycleState…) │ StatusAggregator │ RefreshScheduler │
│ NotificationEngine │ ActionRunner │ CacheStore │ │
│ 抽象: IConnectionProvider / IWidgetProvider / IActionExecutor / ISecretStore │
└──────┬─────────────────┬──────────────────────┬─────────────────┬────────────┘
│ │ │ │
Beacon.Connections Beacon.Actions Beacon.Storage (二期: Plugin 拆分)
GitHub/HTTP/用量 Url/Http/Command JSON 配置/Cache/
(六类连接) DPAPI Secrets/导入导出
数据流(单向):
RefreshScheduler → Connection/WidgetProvider 拉取
→ WidgetState(Severity + payload + fetchedAt)
→ Event Bus
├→ StatusAggregator → 胶囊 / 托盘 / L0 状态灯
├→ NotificationEngine → 规则匹配 → Toast + 通知中心
└→ UI(L0/L2/L3)缓存优先渲染,后台刷新后增量更新
4. 核心领域模型¶
4.1 Severity(统一五级,全产品唯一口径)¶
| 级别 | 视觉 | 语义 |
|---|---|---|
Info |
蓝(系统强调色) | 中性信息(PR opened、release) |
Success |
🟢 绿 | 正常(build succeeded、healthy) |
Warning |
🟡 黄 | 需留意(running 超时、慢、重试中) |
Error |
🔴 红 | 失败(build failed、连接错误) |
Critical |
🔴 红脉冲 | 需立即处理(部署失败、agent 崩溃) |
(派生)Offline |
⚪ 灰空心 | 连接不可用,显示 Last update |
Provider 不允许自定义状态,只能映射到以上枚举 + LifecycleState。
颜色可配置(用户确认诉求:「icon 颜色我都可以配置」):上表为默认调色板;config.json appearance.severityColors 可按级别全局覆盖,widgets.json colorOverride 可按 Widget 实例覆盖(优先级 Widget > 级别 > 默认,非法值回退默认)。L0 tile / L1 胶囊 / L2/L3 状态点 / 托盘图标共用同一套覆盖后的调色板(schema 见 §9.1)。
CI/Workflow 统一生命周期:Queued / Running / Success / Failed / Cancelled / Skipped / Unknown(UI 与 CI 系统解耦)。
4.2 配置模型(JSON)¶
// connections.json —— 只存 credentialRef,不存 token
{
"id": "conn-github-main",
"type": "github",
"endpoint": "https://api.github.com",
"credentialRef": "beacon/github-main",
"settings": { "owner": "cuihairu", "repos": ["example"] }
}
// widgets.json
{
"id": "w-ci-client",
"type": "github.actions.runs",
"connectionId": "conn-github-main",
"config": { "repo": "cuihairu/example", "workflow": "build-client.yml", "branch": "main" },
"refreshTier": "ci",
"pinned": false,
"pinLayout": null // pinned=true 时由 pins.json 承载布局(此处为便利引用)
}
// pins.json(L0 悬浮组件布局,见 §6.2;嵌套在 tile.layout 下,枚举 camelCase 序列化)
{
"tiles": [{
"widgetId": "w-ci-client",
"layout": {
"monitor": "DEL-U2723QE-3840x2160",
"anchor": "bottomRight",
"offsetDips": { "x": 24, "y": 120 },
"collapsed": false,
"floatingX": null, // 独立悬浮框形态的窗口位置(物理像素);panel 模式不写
"floatingY": null
}
}]
}
4.3 WidgetState(运行时状态)¶
WidgetState = { widgetId, widgetType, connectionId, severity, lifecycle?, summary,
detailUrl?, progress?, payload, fetchedAt, isStale, connectionHealthy }
4.4 状态聚合¶
总体状态 = 所有 Widget 的 max severity(Critical > Error > Warning > Info > Success)。胶囊/托盘只回答「有没有事」,点击后 L2 回答「什么事」,L3 回答「细节与操作」。
5. Provider 抽象¶
public enum Severity { Info, Success, Warning, Error, Critical }
public enum LifecycleState { Queued, Running, Success, Failed, Cancelled, Skipped, Unknown }
public interface IConnectionProvider {
string ConnectionType { get; } // "github" | "http" | "bigmodel" | "claude" | "kimi" | "deepseek"
Task<ConnectionHealthState> TestAsync(ConnectionConfig connection, ConnectionContext context, CancellationToken ct);
}
public interface IWidgetProvider {
WidgetTypeDescriptor Descriptor { get; } // 含 pinSupported、refreshTier 建议
Task<WidgetState?> GetStateAsync(WidgetConfig widget, ConnectionConfig connection, ConnectionContext context, CancellationToken ct);
}
public interface IActionExecutor {
string ActionType { get; } // "open.url" | "http" | "gh.workflow_dispatch" | "local.command"
Task<ActionResult> ExecuteAsync(ActionConfig a, CancellationToken ct);
}
public interface ISecretStore {
Task<string?> GetAsync(string credentialRef);
Task SetAsync(string credentialRef, string secret);
Task DeleteAsync(string credentialRef);
}
规则:
- Provider First:GitHub 不进 Core;Core 只有接口 + 调度 + 聚合。
- 同一 Connection 被多 Widget 复用,凭据只配一次。
- GitHub Provider 必须实现 ETag 条件请求(304 不计数)与 rate-limit 感知(剩余额度低于阈值自动拉长间隔)。
- CI Provider(GitHub Actions / Jenkins / GitLab)统一映射到
LifecycleState。 - 二期把 Provider 拆成独立程序集(Plugin),MVP 只需保证接口边界干净。
6. 显示层架构:状态显示与悬浮组件设计(专节)¶
6.1 四层交互模型(Progressive Disclosure)¶
在原三层(胶囊 / 快捷面板 / 详情)之下增加 L0 悬浮组件(Pinned Widget),形成四层:
常驻入口:Tray(任意层级可达)+ Global Hotkey(Ctrl+Alt+B)
L0 Pinned Widgets —— 两种形态(设置「悬浮形态」二选一):宿主面板(默认,
每显示器一个透明置顶宿主窗口,单窗多 tile)/ 独立悬浮框
(每钉选组件一窗,桌面任意拖放);均 NoActivate,只看不弹
┌────────────┐ ┌────────────┐ ┌──────────────┐
│ 🔴 CI 1 │ │ 🟡 PR 3 │ │ ⏳ build 62% │ 单 Widget 钉住:五级状态灯 + 极简数字
└─────┬──────┘ └─────┬──────┘ └──────┬───────┘ 只放变化敏感内容,分级刷新;离线显示 Last update
│ 点击 │ 点击 │ 点击
▼ ▼ ▼
L1 Status Capsule —— 聚合所有 Widget:overall = max severity
┌─────────────────────────────┐
│ 🟢 18 🟡 3 🔴 1 │
└─────────────┬───────────────┘
▼ 点击
L2 Quick Panel —— 概览 + 最近事件 + 一键操作
┌────────────────────────────────────────┐
│ Overview GitHub 12 · CI 3 · Agents 4 │
│ Recent 🔴 Server Build Failed │
│ 🟡 Client Build Running │
│ [Build] [Test] [Deploy] │
└─────────────┬──────────────────────────┘
▼ 需要细节
L3 Detail Window —— 单一对象的完整细节与全部操作
┌────────────────────────────────────────┐
│ Build #1024 · Failed · 12m31s · main │
│ Stage: Compile Error: … │
│ [Open GitHub] [Retry] [Cancel] │
└────────────────────────────────────────┘
| 层 | 载体 | 可见性 | 职责 | 焦点 |
|---|---|---|---|---|
| L0 Pinned Widget | 宿主面板形态:透明置顶宿主窗口内的 tile;悬浮框形态:每组件一窗 | 常驻(可收起) | 单 Widget 的变化敏感状态 | 永不抢焦点 |
| L1 Status Capsule | 独立置顶常驻窗口(CapsuleWindow) |
常驻(设置可关) | 全局聚合「有没有事」 | 永不抢焦点 |
| L2 Quick Panel | 独立 Flyout 窗口 | 点击出现,失焦/ESC 关闭 | 概览 + 快捷操作 | 可激活 |
| L3 Detail Window | 独立窗口 | 点击出现 | 单对象细节 + 全部 Action | 可激活 |
各层是下钻关系而非并列 Dashboard:上层永远更小、更安静;问题出现时颜色变红并上浮一层入口。
6.2 L0 悬浮组件(Pinned Widget)设计¶
6.2.1 创建与开关¶
- 设置逐个开关:Settings → Widgets 列表,每行一个「钉到桌面」开关。
- 右键 Pin:L2 中 Widget 项右键 → Pin to desktop / Unpin。
- 两种入口写同一配置位(
widgets.json的pinned字段),布局落在pins.json。 - 仅
Descriptor.pinSupported == true的 Widget 类型可钉(准入策略见 6.2.5)。
6.2.2 形态规范:小胶囊¶
常态 警告 失败 收起(吸边) 离线
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 🟢 CI 1 │ │ 🟡 CI 12m│ │ 🔴 CI 1 │ ‖(边缘细条/圆点) ⚪ CI Last 10:31
└──────────┘ └──────────┘ └──────────┘
- 尺寸:高 32 DIP,宽随内容(48–120 DIP),圆角胶囊,亚克力/半透明背景,随主题(Light/Dark)。
- 构成 = 状态灯 + 标签 + 极简数字/进度,无正文文字。进度型(构建中)显示百分比或细进度条。
- 状态灯使用 §4.1 统一视觉(默认
Info 蓝 · Success 绿 · Warning 黄 · Error 红 · Critical 红脉冲 · Offline 灰空心),颜色支持按级别/按 Widget 用户覆盖(§4.1 调色规则)。 - 动效遵循 §6.2.8:默认(reduced 档)仅状态变色过渡 + Critical 脉冲(≤1Hz);更丰富的动效族(呼吸灯/闪烁/滑入)由用户在设置中开启,可整体关闭。
6.2.3 窗口与位置行为¶
| 行为 | 规格 |
|---|---|
| 置顶 | Topmost,但 WS_EX_NOACTIVATE:点击不抢焦点,当前输入焦点不被打断 |
| 可拖动 | 左键按住 tile 拖动(3px 死区);拖动中吸附四边(SnapDips=16),拖至屏边收起为细条 |
| 吸边收起 | 贴死边缘拖放 → 收起为细条/圆点(近边松手回弹展开防误触);悬停展开、水滴态点击=弹回展开、展开态点击进 L2;收起状态持久化 |
| 多显示器位置记忆 | 按「显示器标识 + 锚点角 + DIP 偏移」持久化;显示器热插拔/RDP 断连后按锚点恢复;恢复失败(如分辨率变小越界)自动回收到最近合法锚点角 |
| DPI 自适应 | PerMonitorV2;监听 DPIChanged,tile 以 DIP 布局按显示器缩放 |
| 多显示器 | 宿主面板形态:每个显示器一个宿主窗口,tile 只能在所属显示器宿主内;跨显示器拖动 = 迁移 pinLayout 记录。独立悬浮框形态:每组件一窗,位置按组件各记(floatingX/Y 物理像素) |
6.2.4 渲染架构:单窗口多组件共享渲染¶
- 默认(宿主面板形态)不做每个 pinned widget 一个 HWND(窗口数量爆炸、DPI/hit-test 各自维护)。做法:每显示器一个透明分层宿主窗口(
PinnedHostWindow),内部以 tile 列表布局渲染全部该显示器的 pinned widgets。 - 独立悬浮框形态(
pinDisplayMode=floating,重启生效,B-701 扩展):每钉选组件一个独立窗口(FloatingTileWindow,168×32 DIP,Topmost +WS_EX_NOACTIVATE+WS_EX_TOOLWINDOW),桌面任意拖放,位置按组件各记(PinLayout.FloatingX/Y物理像素,未拖过按序级联)。设计初衷的「窗口数量爆炸」顾虑由「用户显式选择 + 组件数量少(钉选通常 <10)」化解;两形态共用同一PinTile渲染与点击下钻链路。 - L1 胶囊(
CapsuleWindow)是独立窗口,不随悬浮形态变化;宿主面板形态下与 L0 tile 同为置顶 NoActivate 常驻物——「单窗口多组件共享渲染」指宿主面板内的 L0 tile 共享同一渲染管线、事件处理、DPI 处理。 - L1 胶囊 = 宿主窗口中的一个常驻聚合 tile,与 L0 tile 共享同一渲染管线、事件处理、DPI 处理——这是「单窗口多组件共享渲染」的具体含义。
- 宿主窗口
ClickThrough=false(要收点击),仅 tile 矩形参与 hit-test,空白区域点击穿透到桌面。 - tile 渲染走 Composition(视觉层 + 属性动画),状态变化只更新对应 tile 的视觉属性,不重建整窗。
6.2.5 内容准入:只放「变化敏感」内容¶
L0 只承载用户需要被动感知变化的信息,由 Widget 类型元数据声明:
| 可钉(pinSupported) | 不适合钉 |
|---|---|
| CI 状态灯、PR 计数、构建进度、Agent 在线状态、机器健康摘要 | Releases 列表、Commit 流、纯静态/低频内容、长文本类 |
- 刷新沿用 §7 分级策略(CI 10–30s、PR 30–120s…),L0 不引入新刷新频率,不钉的 Widget 照常刷新,只是不上桌面。
- 多个 tile 共用同一 Connection 时共享同一条轮询(调度器按
(connection, tier)合并),不会 N 个 tile = N 倍请求。
6.2.6 离线与错误降级¶
- 连接不可用时:tile 变
Offline 灰空心灯,显示Last update HH:mm(悬停 tooltip 或窄字号),绝不弹异常对话框。 - 恢复后自动回到正常态。
isStale(数据超过 2× 刷新周期未更新)在 tooltip 标注。 - L2/L3 中对应 Widget 显示「⚠ Unable to refresh · Last update 2 minutes ago · [Retry]」。
6.2.7 只看不弹原则(L0 铁律)¶
- L0 只展示状态、支持点击下钻(点击 tile → 打开 L2 聚焦到该 Widget,或直接进 L3)。
- L0 不产生任何弹窗/Toast/声音——Alert 属于 NotificationEngine(§8);L0 对 Alert 的唯一表达是状态灯变色。
- L0 不承载需要确认的 Action(Deploy/Restart 这类只出现在 L2/L3);最多允许「打开链接」类零风险动作。
- L0 永不抢焦点、不出现在 Alt-Tab、不进任务栏。
6.2.8 配色与动效(「桌面角落在替我盯着」的表现层)¶
用户效果确认(原话):「我关心的状态直接悬浮在桌面显示,icon 颜色我都可以配置,最好加点动效。」据此把表现层从「纯静态」修订为「颜色可配 + 默认适度动效、可调可关」:
| 项 | 规格 |
|---|---|
| 调色 | 默认五级 + Offline 色表;appearance.severityColors 按级别全局覆盖、colorOverride 按 Widget 覆盖(优先级 Widget > 级别 > 默认,非法值回退默认);L0/L1/L2/L3/托盘同源换色(§4.1) |
| 动效档位 | appearance.motion.mode:full / reduced(默认)/ off;motion.intensity 0.5–2.0 缩放时长与幅度 |
| 动效族 | ① 状态变色过渡(旧色→新色交叉淡变 ~200ms)② 提醒闪烁(状态变化后短促 2 次)③ 呼吸灯(明暗缓慢循环,full 档常驻)④ 脉冲(Critical 默认,≤1Hz)⑤ 滑入(tile 首次出现/收起展开) |
| 约束修订 | 原「桌面常驻物不允许持续动画耗电」从铁律降级为默认值:reduced 档只有变色过渡 + Critical 脉冲;full 档允许常驻动效(用户自担功耗);off 档全静止。不变:动效永不抢焦点、不发声、不产生弹窗(§6.2.7 铁律照旧) |
| 实现 | Composition 属性动画(ColorKeyFrame/ExpressionAnimation)只作用于对应 tile 的视觉层,不重建整窗;窗口不可见/收起态暂停动画循环 |
6.3 L1 Status Capsule(摘要)¶
独立置顶常驻窗口(CapsuleWindow,屏幕右下角,位置记忆),显示各 severity 计数;点击打开 L2。可在设置中关闭胶囊只留 L0,反之亦然(showCapsule)。
6.4 L2 Quick Panel(摘要)¶
Flyout 窗口,出现在胶囊/托盘附近,380 × 540 DIP(master-plan §3.2 建议带取中,批3-补27④):
Overview(按来源计数)+ NEEDS ATTENTION / RUNNING 当前状态三分区(Core QuickPanelSections 判定:
Warning 及以上 / 数据过期 / 连接断 = 需要关注;Running/Queued 无告警 = 正在运行;空区隐藏)+
Recent Events(滚动最近状态变化,即 master-plan「最近状态」)+ 通知中心(notifications.json 落盘水合,单击标已读)。
单击行进 L3,右键 Pin to desktop 落 widgets.json。可激活以支持键盘(ESC 关闭、Tab 导航);
常驻档(config.keepQuickPanelOpen,托盘菜单 Keep Panel Open 打勾)失焦不自隐,Esc/再次点托盘仍可显式关。
6.5 L3 Detail Window(摘要)¶
单对象完整视图:状态、仓库/分支、时长、失败阶段、错误摘要 + 全部 Action(Open / Retry / Cancel / Open Logs)。从 L2 事件或 L0 tile 点击进入。
6.6 层间数据一致性¶
四层渲染同一份 WidgetState(Event Bus 广播),任何一层不存在独立拉取逻辑——保证胶囊、tile、面板、详情显示的状态与时间戳完全一致。
7. 刷新、缓存与离线¶
7.1 分级刷新(唯一策略表)¶
| 数据类别 | 周期 | 备注 |
|---|---|---|
| GitHub PR | 30–120s(默认 60s) | ETag 条件请求 |
| CI / Actions | 10–30s(默认 15s) | Running 态取下限,Queued 取上限 |
| Machine | 15s(区间设计 5–30s,配置可覆盖) | 已实现 |
| Agent | 10s(区间设计 5–15s,配置可覆盖) | 已实现 |
| Workflow 执行 | 事件优先 / 10s 跟随 | 触发后短轮询直至终态 |
| 静态配置 | 手动 | — |
- 抖动 ±20% 防齐发;失败指数退避(×2^n,封顶 10×),成功后复位。
- 调度键 =
(connectionId, tier),同连接同级别 Widget 合并一次拉取后分发。
7.2 缓存优先启动¶
启动 → 读 cache\{connectionId}\last-known-state → 立即渲染(标记 fetchedAt)
→ 后台按分级刷新 → 状态变化经 Event Bus 增量更新 UI
Beacon 打开永远不白屏等网络。
7.3 离线模式¶
断网/连接失败进入降级态:Last known state 可见 + Offline · Last update 10:31 横幅(L1/L2)或灰灯(L0);网络恢复自动重连并复位退避。
8. 通知引擎¶
通知不是裸 Toast,是规则求值:
{
"id": "rule-ci-failed",
"widgetType": "github.actions.runs", // null = 匹配所有类型;另有 widgetId 精确匹配
"severityAtLeast": "Error", // 与可选 severityAtMost 构成频带(如「仅 Warning」)
"toast": true,
"sound": true,
"cooldown": "00:10:00" // TimeSpan 序列化值;null = 同一阈值期间只提醒一次
}
- 规则引擎输入 = WidgetState 变化事件;表驱动,可单测。
- 内置默认规则(无需配置即生效):CI Failed → Toast;运行超 15min → Warning。
- 去重:同 widget 同 severity 冷却期内不重复提醒;状态转好可发 Success 通知。
- 通知记录滚动保留(默认 200 条)+ 已读/未读;L2 Recent Events 与通知中心同源。已落盘(2026-10-09):
notifications.json持久化滚动记录与已读态(启动水合 + RecordsChanged 即存;重启后在告警期的组件按阈值跨越语义重新提醒一次,属预期)。 - 通道:Windows Toast(§10)+ 托盘图标着色 + L0/L1 状态灯。
9. 配置、密钥与导入导出¶
9.1 本地存储(Local First,%AppData%\Beacon\)¶
%AppData%\Beacon\
├── config.json # 全局设置(hotkey、startup、appearance、refresh 覆盖、通知规则)
├── connections.json # 连接元数据(credentialRef,无明文密钥;enabled 启停)
├── widgets.json # Widget 实例(含 pinned 标记与 pinLayout 便利引用)
├── pins.json # L0 tile 布局(嵌套 layout:锚点/偏移/收起态 + floating 悬浮框位置)
├── notifications.json # 通知记录 + 已读态(滚动保留跨重启,JsonNotificationRecordStore)
├── cache\{connId}\states.json # last-known-state
├── secrets.bin # DPAPI(CurrentUser) 加密的密钥库
└── logs\beacon-YYYYMMDD.log # 按天滚动(保留 7 天)+ crash-*.log 崩溃日志,token 一律脱敏
写入策略:临时文件 + 原子替换 + 上一版备份(防写坏配置)。
config.json 的 appearance/motion 段(调色与动效,§4.1/§6.2.8;B-706/B-707 实现、B-805 设置页承载):
{
"appearance": {
"severityColors": { "info": "#58a6ff", "success": "#3fb950", "warning": "#d29922",
"error": "#f85149", "critical": "#ff3b30", "offline": "#8b949e" },
"motion": { "mode": "reduced", "intensity": 1.0 }
}
}
9.2 Secret Storage¶
- 默认实现:DPAPI CurrentUser 加密文件(
secrets.bin),多密钥无长度限制、随用户漫游受限 appropriately。 - 备选实现:Windows Credential Manager(同一
ISecretStore接口)。 - 配置文件中只出现
credentialRef;任何 JSON 中严禁出现明文 token(导入导出同理,见 9.3)。 - 日志/异常/遥测输出一律脱敏。
9.3 导入导出¶
导出 = config + connections + widgets + pins 的打包 JSON,不含 secrets;导入后对每个 credentialRef 提示重新录入。
10. Windows 平台集成点¶
| 能力 | 方案 | 备注 |
|---|---|---|
| 打包形态 | Unpackaged(自包含 exe) | 便携、%AppData% 直写、开发迭代快;二期可加 MSIX |
| System Tray | Win32 Shell_NotifyIcon 自实现(TrayIconService,不引第三方托盘库) |
NOTIFYICONDATA + 消息窗回调;图标按配置色 GDI+ 运行时绘制(B-706) |
| Toast 通知 | Microsoft.Toolkit.Uwp.Notifications(ToastNotificationManagerCompat) |
unpackaged 应用由 Compat Initialize 首次触碰时写 HKCU\Software\Classes\AppUserModelId\ |
| Global Hotkey | RegisterHotKey(默认 Ctrl+Alt+B) |
设置可改;冲突时提示重设 |
| 开机自启 | HKCU Run 键 |
MSIX 化后改 StartupTask |
| 置顶/不激活 | AppWindow.IsAlwaysOnTop + WS_EX_NOACTIVATE interop |
见 §16 风险 1 |
| DPI | PerMonitorV2 manifest | 宿主窗口处理 DPIChanged |
| 多显示器 | 宿主面板形态:每显示器一个 PinnedHostWindow;悬浮框形态:每组件一 FloatingTileWindow | 监听显示拓扑变化重建/越界回收 |
| 单实例 | 命名 Mutex | 二次启动唤起已有实例 |
| 崩溃兜底 | 全局异常 → 写日志 + 托盘气泡,不闪退不留僵尸窗口 |
11. 进程与线程模型¶
- 单进程。UI 线程(DispatcherQueue)只做渲染与交互;所有网络/命令在后台线程。
- Event Bus 线程安全;UI 订阅方负责 marshal 回 UI 线程。
- Action 执行:独立 Task + 超时 + 取消;local command 捕获输出写缓存,供 L3「Open Logs」。
- 长时动作(workflow_dispatch 后的构建)不在 Beacon 进程内等待——以状态轮询驱动 UI,Beacon 不阻塞。
12. 错误处理¶
- 分层降级:网络错误只影响对应 Connection 的 Widget(变 Offline/黄灯),绝不让 Beacon 崩溃或整窗报错。
- 用户可见文案统一:「⚠ Unable to refresh · Last update 2 minutes ago · [Retry]」——永远给出最后已知时间与重试入口。
- 所有异常进滚动日志;GitHub 401/403(token 失效/限流)给专门提示(「Token 已失效,去设置更新」)。
13. 安全考量¶
- GitHub PAT 最小授权:
repo+workflow,不请求 admin 域。 - Secrets 只经
ISecretStore;内存中 token 不落日志/崩溃转储。 - local command Action 默认要求执行前确认(可按 Action 关闭);命令内容在设置中明文可见。
- 导出文件不含密钥(§9.3);卸载不残留 secrets.bin 之外的敏感数据。
14. 可扩展路线(Provider → Plugin)¶
- MVP:Provider 以程序集内文件夹组织(
Beacon.Connections/GitHub|Rest),接口即边界。 - 二期:Provider 拆独立程序集(
Beacon.Connections.Jenkins等),走 .NET 插件加载(AssemblyLoadContext)+ 清单注册;Plugin 可贡献 Connection/Widget/Action/Trigger。【冻结 2026-10-11】(master-plan §2.3「不做复杂插件平台」拍板):接口边界保持干净,程序集拆分与插件加载按需再议。 - Agent/中央服务器同样以 Connection + Provider 接入,不写死进 Core。【冻结 2026-10-11】(master-plan §2.3「不做 AI Agent 管理/远程机器管理/控制中心」拍板)。
- 二期/三期增强层:本地 AI 支持(Ollama / OpenAI-compatible API)——「Ask Beacon」收集 CI 结果、日志、提交上下文交模型分析(原方案有此项,归二期不丢);AI 只是增强层,不进 Core 架构。【冻结 2026-10-11】(master-plan §2.3 拍板:维护成本优先)。
15. 测试策略¶
| 层 | 方式 |
|---|---|
| Core(聚合/调度/规则引擎/退避) | xUnit 单测,表驱动;时钟注入(模拟退避/冷却) |
| Connections | Fake HttpMessageHandler + GitHub API 响应夹具;ETag/限流分支覆盖 |
| Storage | 配置读写往返、原子写、secrets 加解密(Windows 环境) |
| UI(四层交互) | 手动验收清单(随 Issue 附带);WinUI 无法在 Linux CI 跑 |
| CI | GitHub Actions windows-latest:build + test(P0 建立) |
16. 风险与开放问题¶
| # | 风险 | 缓解 |
|---|---|---|
| 1 | WinUI 3 的 Topmost + NoActivate + 点击穿透组合需 Win32 interop,行为需验证 | 隔离为 PinnedHostWindow 单类;兜底方案:改用纯 Win32 分层窗口承载 Composition 内容 |
| 2 | unpackaged Toast 的 AUMID 快捷方式在某些环境注册失败 | 启动时自检并修复快捷方式;兜底退化为托盘气泡 + 声音 |
| 3 | GitHub rate limit(多仓库多 Widget) | ETag 必选 + (connection,tier) 合并轮询 + 额度感知退避(§5/§7) |
| 4 | 多显示器热插拔/RDP 导致 tile 丢失 | 锚点角恢复策略 + 越界回收(§6.2.3);显示拓扑变化事件驱动重建 |
| 5 | 范围膨胀回「大 Dashboard」 | 非目标清单(§2.2)写进每个 Issue 的评审基线;Issue 列表外的需求一律入二期 |
| 6 | ~~开放问题:胶囊与 L0 是否允许分开在不同显示器~~ 已决策:允许 —— 胶囊本身即常驻 tile,pins.json 按显示器存锚点,天然支持胶囊与 L0 tile 分布在不同显示器 | 显示拓扑变化时各显示器宿主独立恢复 |
| 7 | ~~桌面常驻物不允许持续动画(防耗电)~~ 已决策(用户效果确认):降级为默认值 —— 「我关心的状态悬浮桌面、颜色可配、最好带动效」:动效三档 full/reduced/off(默认 reduced)+ intensity 可调,颜色按级别/按 Widget 覆盖 | 设置页(B-805)提供三档+强度+调色板;窗口不可见/收起暂停动画;L0 铁律(不抢焦点/不发声/不弹窗)不变 |
| 8 | ~~技术栈:.NET 8~~ 已决策(用户点名+拍板,2026-10-07):升 .NET 10 LTS —— .NET 8 于 2026-11 EOL 在即,.NET 10 为现行 LTS,舰队口径钉官方最新稳定 | 全系 TFM net10.0 / net10.0-windows10.0.19041.0;global.json pin 10.0.100(rollForward latestFeature);CI setup-dotnet 10.0.x;WindowsAppSDK 与 .NET 10 兼容性在 CI 编译验证,不兼容则升版并回记本行 |
17. 里程碑与下游¶
| 里程碑 | 内容 | Issue |
|---|---|---|
| M1 可常驻 | 工程地基 + Shell(Tray/胶囊/Hotkey/自启) | P0–P1 |
| M2 有内核 | 配置/Secrets/Event Bus/调度/聚合/缓存/离线 | P2 |
| M3 GitHub 闭环 | PR/Actions Provider + 四类 Action | P3–P4 |
| M4 会提醒 | Toast + 通知引擎 + Quick Panel/Detail | P5–P6 |
| M5 = MVP | L0 悬浮组件 + Settings + 导入导出 + 验收发布 | P7–P8 |
- 项目/程序集划分与脚手架命令:solution-项目目录
- 可执行任务拆解(43 个 Issue,含验收标准):MVP-Issue列表