跳转至

English | 中文

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 目标

  1. 开机自启、常驻(Tray + 桌面悬浮层),不打扰。
  2. GitHub(PR / Actions)状态聚合为统一 Severity,桌面可见。
  3. 失败发生时主动 Toast;点击即达失败详情。
  4. 一键 Run / Retry / Cancel / Open,全程不开浏览器。
  5. 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 铁律)

  1. L0 只展示状态、支持点击下钻(点击 tile → 打开 L2 聚焦到该 Widget,或直接进 L3)。
  2. L0 不产生任何弹窗/Toast/声音——Alert 属于 NotificationEngine(§8);L0 对 Alert 的唯一表达是状态灯变色。
  3. L0 不承载需要确认的 Action(Deploy/Restart 这类只出现在 L2/L3);最多允许「打开链接」类零风险动作。
  4. 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\ 注册键(含 CustomActivator),缺失下次启动重建自愈;toolkit 7.1.3 不创建 .lnk(Show 只调 CreateToastNotifier().Show);激活路由回 Beacon(深链到 L3)
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