Files
geo/docs/superpowers/specs/2026-04-18-electron-desktop-client-design.md
T
root 9ec9314aea docs(desktop-client): add Electron 桌面客户端 replacement design spec
Captures the full plan for replacing apps/browser-extension with an
Electron tray client: single main process with multi-session isolation,
embedded web-view login with OS-encrypted local cookie vault, CDP-based
LLM monitoring (no official APIs, cost-driven), unified PublishAdapter
and MonitorAdapter interfaces with dual-track updates (per-adapter patch
channel + full release channel), Playwright-grade tracing, new
/api/desktop/* protocol with SSE + 60s pull, and 5-arch multi-OS
packaging strategy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 20:28:42 +08:00

26 KiB
Raw Blame History

Electron 桌面客户端设计(全面替换 WXT 浏览器插件)

状态Draft · 待实施 作者@liangxu + Claude 日期2026-04-18 范围:新增 apps/desktop-client//api/desktop/* 服务端路由、相关 DB schema;删除 apps/browser-extension//api/plugin/* 路由及相关代码。


1. 背景与动机

现有 apps/browser-extension/WXT MV3 Chrome 扩展,"GEO Publisher")同时承担两类工作:

  1. 向 13 家中国媒体平台做内容分发(微信公众号、头条号、百家号、搜狐号、网易号、ZOL、懂车帝、知乎、企鹅号、简书、掘金、B 站、什么值得买)。
  2. 监控主流 LLM(豆包、千问、DeepSeek)在给定查询下的输出,判定品牌/KOL 是否被引用。

当前问题:

  • LLM 抓取不稳定。监控模块依赖 DOM 选择器与 webRequest.onBeforeRequest 在 MV3 下受限,无法可靠拿到 SSE 响应 Body;平台 UI 一变就挂。
  • 适配器受 MV3 约束。后台 service worker 频繁休眠,长任务难以保证完成;跨平台复杂登录(QR、滑块、SMS)在扩展内体验差。
  • 不可扩展到真正的系统级自动化。系统托盘、开机自启、多账号并发 session 隔离、进程级 CDP 调试,这些 MV3 都做不到。

因此:全面替换为一个 Electron 桌面客户端,承担上述全部职责。浏览器扩展停止维护并下线。


2. 设计硬约束(已确认)

  1. 全面替换,不做"先 monitoring 后 publishing"的分阶段替换。
  2. 不做向后兼容。因为是开发版本阶段,服务端协议、DB schema、认证字段按桌面客户端的最佳形态重新设计,不为插件保留旧字段或旧路由。
  3. 登录走嵌入式 Web View。用户在 Electron 真实平台登录页里自己完成扫码/密码/短信/滑块;我们只保存 cookie,不保存账号密码。
  4. Cookie 本地存储 + OS 级加密Electron safeStoragemacOS Keychain / Windows DPAPI / Linux libsecret)。服务端永不存储 Cookie 或任何平台凭据。
  5. LLM 监控一律走网页 + CDP 拦截,不调用任何 LLM 官方 API(成本导向;多账号 = 多配额,线性成本比 token 计费友好)。后端 llm.api_key 是后端自用管线,与客户端监控完全解耦。
  6. 不打包 Playwright。复用 Electron 自带 Chromium,通过 webContents.debugger 直接用 CDP。包体越小越好。
  7. 支持 5 目标Windows x64/arm64、macOS Intel+Apple Silicon、Linux x64/arm64。
  8. 一个微信号只给一个 workspace——不支持跨租户共享账号。

3. 架构总览

3.1 进程拓扑

单 Electron 进程树(方案 A)。

  • Main 进程Node):任务调度器、Session 注册表、加密凭据仓库、CDP debugger 管理、与后端 REST/SSE 通信、Tray & autoUpdater。
  • Renderer 进程(托盘窗口 / 发布预览窗口):Vue UI,复用 apps/admin-web 的组件库与 i18n。禁用 nodeIntegration,通过 contextBridge 暴露窄接口。
  • 工作 BrowserView:每个登录账号一个 persist:<account-id> session + 一个隐藏 BrowserView,挂在离屏 BrowserWindow 上。常驻但不可见;需要操作时 webContents.debugger.attach()
  • 只有当实测 CDP 流式抓取会让 UI 卡顿时,才把 debugger 交互迁到 utilityProcess,其他模块保持不动。

3.2 模块分层

新增 apps/desktop-client/,作为 pnpm workspace 的新包:

apps/desktop-client/
  main/
    bootstrap.ts          # app ready, single-instance-lock, tray, autoUpdater
    session-registry.ts   # account-id → BrowserView + session
    vault.ts              # safeStorage 包 cookie/tokenSQLite 持久化
    scheduler.ts          # 本地 cron + 服务端 task lease 合流
    circuit-breaker.ts    # 平台级熔断
    keep-alive.ts         # 探活 / 保活调度
    crash-recovery.ts     # 启动时扫描 in-flight 任务
    tracing/
      recorder.ts         # 启停;订阅 CDP Page/Network/Fetch/Runtime;采样截屏
      trace-writer.ts     # 流式写入 .trace zip
      redactor.ts         # Cookie / Authorization / password 字段自动脱敏
      viewer-assets/      # 内置 trace viewerVue 单页,离线)
    transport/
      api-client.ts       # 复用 packages/http-client,注入 client_token
      sse-client.ts       # /api/desktop/events SSE 长连接 + pull 兜底
    adapters/
      _shared/
        debugger-helper.ts
        dom-helper.ts
        upload-helper.ts
        captcha-pause.ts
        selectors.json    # 可热更的选择器表
      base.ts             # PublishAdapter / MonitorAdapter 接口
      wechat.ts ... smzdm.ts  # 13 个发布适配器
      doubao.ts qwen.ts deepseek.ts  # 3 个 LLM 监控适配器
      index.ts            # 注册表
  preload/
    bridge.ts             # 暴露给 renderer 的受控 API
  renderer/               # 复用 apps/admin-web,打 Electron 专属 build

3.3 关键复用

  • packages/shared-types:平台枚举、任务类型、DTO。
  • packages/http-clientHTTP 基座。
  • apps/admin-web 的 Vue 组件与路由:删掉 SaaS 专属页面后作为 renderer 的基座。
  • apps/browser-extension/src/adapters/* 的选择器和工作流:批量搬进来,套一层 runInSession(accountId, fn) 即可复用。

4. 账号 & 会话模型

4.1 数据结构

type Account = {
  id: string                 // uuid,本地生成
  platform: PlatformKind     // 'wechat' | 'toutiao' | ... | 'doubao' | 'qwen' | 'deepseek'
  purpose: 'publish' | 'monitor'
  displayName: string
  partitionKey: string       // 'persist:acc-<id>'Electron session 隔离
  createdAt: Date
  lastSeenAt: Date
  health: 'live' | 'expired' | 'captcha' | 'risk'
  tenantId: string
  workspaceId: string
  keepAlive: {
    mode: 'probe-only' | 'heartbeat' | 'disabled'
    probeIntervalMs: number      // 默认 30 min
    heartbeatIntervalMs?: number // 默认 8 min,仅 heartbeat 模式
    lastProbeAt?: Date
    lastHeartbeatAt?: Date
    failureStreak: number
  }
  quota: {
    hourlyBudget: number
    usedThisHour: number
    resetAt: Date
  }
}

4.2 登录流程

  1. 用户在 UI 点「新增账号 → 选平台」。
  2. 主进程 session-registry.create(platform) 分配新 partition,打开一个可见 BrowserWindow(登录专用),加载该平台登录页。
  3. 用户自己用扫码 / 密码 / 短信 / 滑块登录。
  4. 登录探针(preload 脚本 + URL 规则)检测到已登录态后关闭窗口。
  5. Electron 自动把 cookies 持久化到 userData/Partitions/<key>/;同时主进程读取 cookies 用 safeStorage 加密写本地 SQLite 作为元数据 + 加密快照(用于迁机器、备份、健康检查)。
  6. 账号进入"常驻池",分配隐藏 BrowserView 挂在离屏窗口;按需激活 CDP。

4.3 多账号并存

同一平台多账号完全隔离——partition 不同、cookies 不同、UA 不同、可通过 session 级 proxy 走不同 IP。调度器按账号维度限流(默认同一账号并发 1、同一平台并发 3,可配置)。

4.4 Cookie/Session 保活策略

三个档位:

档位 动作 频率 代价
探活 加载主页,检查 DOM 登录标志 30 min
保活 访问平台私有 API 触发 Set-Cookie/token 刷新 8 min(豆包/千问)
按需激活 任务到来前 pre-check 任务驱动
平台类型 账号示例 保活方式 节奏
媒体发布 微信/头条/B 站/知乎 等 13 家 只探活 30 min
豆包 Doubao 主动保活 8 min + 30 min 探活
千问 Qwen 主动保活 8 min + 30 min 探活
DeepSeek DeepSeek 只探活 30 min

实施细节:

  • 错峰:同一平台 N 个账号按 hash(accountId) mod period 分布时间槽 + ±30s 抖动。
  • 优先级:业务任务 > 保活 > 探活。
  • 失败升级:保活失败 1 次立即重试;连 2 次失败降级为探活一次确认状态;探活失败则 health: expired,托盘红点 + OS 通知 → 用户点击弹登录窗(partition 复用,不需要重建账号)。
  • 静音时段:默认 0:00–7:00 不保活(可配置关闭)。
  • 休眠感知powerMonitor.on('suspend'|'resume'),休眠暂停全部保活,唤醒后补一次探活再恢复。
  • 配额池:同一 LLM 多账号时,调度器按"最近使用 + 剩余配额"挑账号。
  • 风控回避:每账号"本小时平台请求数"超阈值就停保活。

4.5 凭据粒度

  • 保存账号密码。
  • 只保存 Cookie + 平台侧必要 token(如 Doubao 的 ms_tokenfp)。
  • 重装客户端 → 从本地加密备份恢复,或重登一次。

5. 适配器模型

5.1 接口定义

interface AdapterContext {
  accountId: string
  session: Session
  view: WebContentsView
  debugger: DebuggerClient
  logger: ScopedLogger
  signal: AbortSignal
}

interface PublishAdapter {
  platform: PlatformKind
  capabilities: { images: boolean; video: boolean; markdown: boolean; /* ... */ }
  probe(ctx: AdapterContext): Promise<HealthState>
  keepAlive(ctx: AdapterContext): Promise<void>   // 默认 no-op
  publish(ctx: AdapterContext, task: PublishTask): Promise<PublishResult>
}

interface MonitorAdapter {
  provider: LLMProvider
  probe(ctx: AdapterContext): Promise<HealthState>
  keepAlive(ctx: AdapterContext): Promise<void>
  query(ctx: AdapterContext, task: MonitorQueryTask): Promise<MonitorResult>
}

5.2 监控适配器标准套路

1. ensureLoggedIn(ctx)     → probe / 必要时触发保活
2. openChat(ctx)           → 在 BrowserView 里导航或复用 tab
3. attach CDP Fetch+Network domain
4. submit query            → 触发网页发出请求
5. collect SSE via CDP.Network.responseReceivedExtraInfo + getResponseBody
6. parse provider-specific payload → normalized MonitorResult
7. detach CDPfinally

核心价值:步骤 5 抓拦截后的原始 API 响应 JSON/SSE,不依赖渲染后 DOM 文本。UI 改变不影响,只有 API schema 变更才需要更新 parser。

5.3 发布适配器模式

每个发布任务或每个账号带 PublishMode

type PublishMode =
  | { kind: 'manual'; requireUserReview: true }   // 填好表单后暂停,等用户点"发布"
  | { kind: 'auto' }                              // 一口气跑到底

状态机:filling → readyToReview → submitting → done。manual 在 readyToReview 挂一个用户 gateauto 不挂。同一份 adapter 代码两种模式共用。

文件上传:protocol.handle('file') + CDP Input.dispatchDragEvent 或直接喂 <input type=file>,不经服务端中转。

5.4 公共能力

adapters/_shared/

  • debugger-helper.ts:封装 CDP attach/detach、SSE stream 收集、超时。
  • dom-helper.ts:语义化选择器("找到登录头像")、重试、容错。
  • upload-helper.ts:文件上传统一入口。
  • captcha-pause.ts:遇滑块/点击验证时把 BrowserView 转前台让用户过,通过后继续。

5.5 双轨更新:局部热更 + 全量

通道 内容 节奏 形式
Patch 通道 单个适配器的 selectors.jsonparsers.ts(纯声明 / 纯函数) 分钟级按需 服务端签名补丁包,存 userData/adapter-patches/<platform>/<version>/下次该适配器的任务开始时动态 import 加载(不重启客户端)
Release 通道 二进制、主进程、UI、适配器宿主框架 周/双周 electron-updater 差分包

Patch 通道硬约束:

  • 只允许替换声明式数据纯函数模块。静态检查拒绝 fs/child_process/Buffer/require/import.meta.url 等导入。
  • 服务端签名 + 客户端 Ed25519 验签。
  • 每个 patch 带 {platform, version, minClientVersion};客户端版本不够忽略。
  • UI 每个适配器显示两行版本号:client 1.2.0 · doubao patch #47

5.6 Trace 基础设施(Playwright Trace Viewer 等级)

记录内容:

类别 内容
CDP 事件流 Network/DOM/Page/Fetch/Runtime 事件时间线
关键截屏 2 fps JPG 或手动截屏
DOM 快照 关键节点 outerHTML
Network 请求+响应,包括 SSE 原始 chunks
日志 console + 结构化

触发策略:

  • 平时:只结构化日志,不录 trace。
  • Rolling buffer:每个适配器保留最近 30 秒内存缓冲;任务失败自动 flush 成 trace,成功则丢弃。
  • 强制录制UI 开关或启动参数 --trace=always
  • 日志等级debug/info/warn/error,按适配器独立调(DEBUG_ADAPTERS=doubao,qwen)。

存储:

  • 位置:userData/traces/<accountId>/<taskId>.trace.zip
  • 配额:默认保留 50 份500 MB(先到先清)。
  • 脱敏:Cookie / Set-Cookie / Authorization / x-xsrf-token / password / 手机号 / email 自动 <redacted>
  • 默认不上传服务端;用户手动点"发送诊断包"时才上传。
  • 文件格式兼容 Playwright Trace Viewernpx playwright show-trace 可直接打开)。

6. 服务端协议

6.1 命名(全部重命名)

plugin_installations / /api/plugin/* / installation_token → 新 desktop_clients / /api/desktop/* / client_token。因为是开发版本、无生产流量,DB 迁移直接 drop 旧表、create 新表,不做导入或双写。

6.2 认证

Token 代表 发放 用途
User JWT 人类用户 /api/auth/login 用户在 Electron 内的交互(新增账号、创建监控计划、看报表)
Client Token 一台 Electron 安装 登录后 /api/desktop/clients/register 换取 后台任务回调、任务拉取、心跳

Client Token 绑定 tenant_id + workspace_id + client_id + os + cpu_arch + client_version,SHA-256 后入库。重装/迁机器 = 重新 register。

6.3 任务分发:SSE + 60s pull 兜底

  • 客户端启动后对 /api/desktop/events 建立 SSE 长连接,server 推 {type:'task', payload}
  • 网络抖/代理不稳时退化为每 60 秒 pull /api/desktop/tasks/lease
  • 两渠道幂等;任务带 task_id + deduplication_key
  • 为什么不用 WebSocketSSE 单向推已足够,穿透 Nginx/Cloudflare 更稳,代码更简单。
  • 为什么不纯 pull:用户在 Web 上点"立即跑一次"需要秒级响应。

6.4 路由表

# 配对
POST /api/auth/login
POST /api/desktop/clients/register          # body: {device_name, os, cpu_arch, client_version}
POST /api/desktop/clients/heartbeat         # client_token, 上报活跃度
POST /api/desktop/clients/metrics           # 轻量非敏感聚合指标

# 事件 & 任务
GET  /api/desktop/events                    # SSE, client_token
GET  /api/desktop/tasks/lease?kind=publish|monitor  # pull 兜底
POST /api/desktop/tasks/{id}/ack
POST /api/desktop/tasks/{id}/result
POST /api/desktop/tasks/{id}/trace          # 用户主动上传诊断包

# 账号元数据(不传 cookie
POST   /api/desktop/accounts
PATCH  /api/desktop/accounts/{id}
DELETE /api/desktop/accounts/{id}

# 管理 UIWeb / Electron UI 共用)
GET  /api/tenant/monitoring-plans
POST /api/tenant/publish-jobs
# ...

6.5 服务端边界

服务端只存任务意图"账号 A 去豆包问 Q")与结果("答案文本 + 品牌是否出现")。永不存 Cookie、永不直连平台帮客户端跑。客户端失联 = 任务排队;长时间拿不到 = 超时失败。硬边界,防止 Cookie 从客户端外泄。

6.6 DB Schema(关键表)

  • desktop_clients: id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, created_at, last_seen_at, revoked_at
  • platform_accounts: id, tenant_id, workspace_id, client_id(FK), platform, display_name, health, created_at, last_seen_at无 cookie / token 列
  • desktop_tasks: id, kind, payload(jsonb), status, client_id, lease_expires_at, created_at, result(jsonb), error(jsonb), dedup_key
  • desktop_task_traces: task_id(FK), size, uploaded_at, object_key

6.7 任务生命周期

Web UI 创建 MonitoringPlan("brand=X on doubao,qwen daily 9am")
      ↓
Scheduler (server cron) 展开成 N 条 desktop_tasks (queued)
      ↓
SSE push → 任意在线的、有对应账号的 desktop_client
      ↓
Client ack → lease_expires_at=+10min → 执行 → result
      ↓
POST /result → server 存结果 → Web UI 可视化

7. 错误处理与可观测性

7.1 错误分层

例子 处理
适配器内部(可重试) 选择器暂未命中、SSE 断流、CDP 瞬时断开 适配器内部指数退避重试,最多 3 次,不升级
账号级 Cookie 过期、风控滑块、封号 health=expired/risk;托盘红点 + OS 通知;任务返回 ACCOUNT_UNAVAILABLE;服务端不对该账号 retry,派给池里其它账号
平台级 全账号连续失败 N 次、Cloudflare 5xx 熔断 15 分钟;新任务返回 PLATFORM_CIRCUIT_OPEN;到时半开探活
客户端级 主进程 uncaughtException、渲染进程崩溃 落本地 crash log → 可选上报 → 优雅重启

7.2 结构化日志

pino JSON,字段约定:ts, level, module, accountId, taskId, stage, event, durationMs, bytes。本地 rolling file10 MB × 20 份)。不默认上传

7.3 指标上报

5 min 一次上报 /api/desktop/clients/metrics,只含非敏感聚合:CPU/内存/事件循环延迟、每平台每账号的任务成功率 + 平均耗时、SSE 连接状态、pull 回退次数。不含查询内容、回答内容、Cookie。

7.4 用户可见反馈

  • 托盘图标三色(绿/黄/红)表示账号健康。
  • OS 通知在需要用户动作时弹(登录失效、捕获到验证码)。
  • UI "事件中心":列最近 50 次失败,点进去直接"重新运行"或"启用录屏再跑"。
  • "发送诊断包"按钮:最近 1 小时日志 + trace 打包脱敏上传,返回 diagnostic_id

7.5 崩溃恢复(幂等)

启动时扫描 desktop_taskslease 属于我但 result 为空 的任务:

  • 未进入 submit 阶段:重新 ack 再跑。
  • 已进入 submit 阶段(已确认):放弃任务,标 UNKNOWN,由人判断。UI 提示"上次崩溃时尚不确定是否发布成功,请去平台确认后手动标注",避免重复发布。

8. 打包、自动更新、签名

8.1 工具链

  • electron-builder 多平台打包 + 差分更新 + 签名。
  • electron-updater 跑在 Main 进程,latest-*.yml + blockmap 差分下发。
  • 渠道stable/beta/dev,用户可切。Patch 热更通道独立,不走 electron-updater。

8.2 目标矩阵

OS Arch 产物 签名
macOS universalIntel + Apple Silicon .dmg + .zip Apple Developer ID + notarytool 公证(已有 Apple Dev 账号,CI 直接接入)
Windows x64 .exe (nsis) 暂无,后期购买(正式发布前阻塞项)
Windows arm64 .exe (nsis) 同上
Linux x64 .AppImage + .deb GPG 可选
Linux arm64 .AppImage + .deb GPG 可选

产物大小目标:单平台 ≤ 120 MB

8.3 CI

GitHub Actionsrunner 矩阵:macos-14 (arm64) + macos-13 (x64) 合并 universalwindows-latest 出 x64 与 arm64ubuntu-latest 出 x64ubuntu-22.04-arm 出 arm64(或 cross-build)。发布 tag v1.2.3 → 打包 → 上传对象存储 + 生成 latest-*.ymlautoUpdater.checkForUpdates() 看到。

8.4 更新 UX

  • 默认"自动下载 + 手动安装"。下好后 Tray 角标提示"新版本可用,点击重启安装"。不打断正在跑的任务。
  • 强制更新:服务端下发 minClientVersion;低于该版本拒绝启动,引导升级。
  • 降级:服务端可下发"建议版本号",客户端比建议版本新时允许一键回滚(electron-updater downgrade: true)。

8.5 应用标识

  • app.requestSingleInstanceLock():禁止同用户开两个副本。
  • Protocol handlergeorankly://,用于 Web 端深链接到客户端。
  • macOS 关闭窗口 ≠ 退出;Windows/Linux 最小化到托盘;退出必须从 Tray 菜单。

8.6 安全加固

  • contextIsolation: truenodeIntegration: falsesandbox: true for renderer。
  • CSP 严格模式;renderer 不允许加载远程 JS。
  • webContents.setWindowOpenHandler 拦截所有 window.open → 系统浏览器。
  • 禁用 <webview> tag,第三方平台一律用 BrowserView

9. 测试策略

9.1 金字塔

┌──────────────┐  手工 + 冒烟 Playbook(每 release
│  E2E 烟雾      │
├──────────────┤  Playwright @ Electron + fake server + fake platforms(每 PR
│  集成 / 行为    │
├──────────────┤  Vitest(每 commit
│  单元(adapter)│
├──────────────┤  tsc --noEmit + eslint + typecheck(每保存)
│  类型 / 静态     │
└──────────────┘

9.2 单元

  • 对象:适配器的 SSE parser(纯函数)与 发布状态机
  • Fixturestests/fixtures/sse/doubao-2026-04.txt(脱敏后真实抓的 SSE)。
  • 平台 payload 变动 → 补 fixture → 测试跟着更新。

9.3 集成

用 Playwright _electron.launch 驱动 Electron,对 本地 fake server + 本地 fake 平台HTTP server 喂 fixture SSE)。13+3 适配器全跑一遍,并行约 3–5 分钟。

9.4 集成 · 协议

客户端 + 真实 server + fake 平台:验证任务分发 / 幂等 / 熔断 / 断线重连

9.5 人工冒烟 Playbook

每次 release 用测试账号:

  • 每发布平台跑一个 draft/private publish → 立即删。
  • 每 LLM 跑 3 条标准查询,对比上版本抓取结构一致性。

记成 md 文档由 QA 跟着跑,半小时完成。

9.6 Trace 回归套件

工具 pnpm dev:replay <trace.zip>:把真实 trace 里的 network responses 重放到 fake 平台,跑 adapter。历史错过的输入永远不再破解析器。

9.7 不做的

  • 不对 electron-builder 打包产物在 CI 跑 E2E(矩阵太贵)。
  • 不对 autoUpdater 真实升级做 CI(单独写 tests/updater-smoke.md 手动脚本)。
  • 不测真平台滑块 captcha——它是概率事件,只测 gate 机制。

9.8 门禁

  • PR:单元 + 集成(fake)全过。
  • Release tag+ 冒烟 Playbook 签字。
  • 每周:trace 诊断报告 review。

10. 上线节奏与回滚

10.1 阶段

阶段 受众 签名 退出门槛
Alpha(内部) 开发 + QA(≤5 人) Mac 公证 / Win 无签名 / Linux 裸 AppImage 13+3 适配器 fake + 真实账号成功率 ≥ 90%
Beta(邀请制) 公司内部 + 合作客户(≤30 人) 同上 连续 7 天无 P0;保活命中率 > 95%;patch 通道应急修复跑通 ≥ 1 次
GA 候选 放开注册 Win 签名必须到位 安装成功率 > 99%crash rate < 0.1%/session

客户端和服务端同加 channelalpha/beta/stable),更新按 channel 拉不同 latest-*.yml。"关于"里可切。

10.2 硬切换落地顺序(开发版本无生产用户)

因为当前还是开发版本,没有真实生产用户在跑旧 /api/plugin/*,不需要过渡期:

  1. 同一个 PR 里:新增 /api/desktop/* 路由 + 新 DB 迁移 + 删除 /api/plugin/* 路由与相关 handler。
  2. 同一个 PR 里:删除 apps/browser-extension/ 整个目录。
  3. 废弃文档 docs/media-publisher-extension-runtime-v1.md,新写桌面客户端运行时文档。
  4. 发 Alpha 客户端;内部账号全部用 Electron 重登。

如果未来发现有内部测试账号遗留在老表里:手动导出账号元数据后 drop 老表,不写自动迁移。

10.3 三层回滚

情景 动作
某 patch 导致单平台适配器坏 服务端把该平台 patch 版本号回滚到上一稳定版;客户端下次任务自动用旧 patch(秒级)
某 release 中等问题 下发"建议版本 ≤ X"electron-updater 帮用户降级(分钟级)
某 release 严重安全问题 下发 minClientVersion = Y;低于 Y 拒绝启动,UI 引导升级

所有回滚只在服务端配置,不动客户端代码。

10.4 运营指标

  • 每平台适配器每日成功率(target:发布 > 95%、监控 > 90%
  • 每次 crash 的 top 堆栈
  • SSE 长连接平均保持时长(target > 30 min
  • patch 推送后"多久 80% 客户端加载新 patch"target < 30 min

11. 已知风险

  1. Win 无签名期间(Alpha/Beta),用户首次安装会遇 SmartScreen 警告,转化率会掉。已接受。
  2. 豆包/千问网页结构变,最坏情况紧急手搓 patch,SLA 按小时算。
  3. 用户跨 OS 切换(Mac → Win 再回来)不迁移登录态(local-only cookies),需全部重登。
  4. 账号被平台风控无法自动恢复;UI 引导用户换号。
  5. 开机自启 + 托盘常驻可能被企业 EDR 误报恶意软件;需准备白名单说明材料。
  6. Electron 基础 RAM 占用~300 MB + 每 BrowserView ~50 MB。10 账号场景约 800 MB,这是 Chromium 本质开销,无法显著优化。

12. 术语表

  • Patch 通道:仅下发适配器的声明式数据和纯函数,分钟级生效,不重启。
  • Release 通道:完整二进制 + 差分升级,通过 electron-updater。
  • 探活(probe:加载主页 DOM 判断登录态,低代价。
  • 保活(keep-alive / heartbeat:主动访问平台私有 API 触发 token 刷新。
  • Client Token:一台 Electron 安装的后台身份,与 User JWT 并列。
  • PartitionElectron session.fromPartition('persist:<id>'),每个账号一个隔离的 cookie/storage 容器。