diff --git a/docs/superpowers/specs/2026-04-18-electron-desktop-client-design.md b/docs/superpowers/specs/2026-04-18-electron-desktop-client-design.md index 06b7bfd..7d9626d 100644 --- a/docs/superpowers/specs/2026-04-18-electron-desktop-client-design.md +++ b/docs/superpowers/specs/2026-04-18-electron-desktop-client-design.md @@ -14,7 +14,7 @@ 现有 `apps/browser-extension/`(WXT MV3 Chrome 扩展,"GEO Publisher")同时承担两类工作: 1. 向 13 家中国媒体平台做内容分发(微信公众号、头条号、百家号、搜狐号、网易号、ZOL、懂车帝、知乎、企鹅号、简书、掘金、B 站、什么值得买)。 -2. 监控主流 LLM(豆包、千问、DeepSeek)在给定查询下的输出,判定品牌/KOL 是否被引用。 +2. 监控主流 LLM(豆包、千问、DeepSeek、混元、Kimi)在给定查询下的输出,判定品牌 是否被引用。 当前问题: @@ -32,7 +32,7 @@ 1. **全面替换**,不做"先 monitoring 后 publishing"的分阶段替换。 2. **不做向后兼容**。因为是开发版本阶段,服务端协议、DB schema、认证字段按桌面客户端的最佳形态重新设计,不为插件保留旧字段或旧路由。 3. **登录走嵌入式 Web View**。用户在 Electron 真实平台登录页里自己完成扫码/密码/短信/滑块;我们只保存 cookie,不保存账号密码。 -4. **Cookie 本地存储 + OS 级加密**(Electron `safeStorage`,macOS Keychain / Windows DPAPI / Linux libsecret)。服务端**永不存储** Cookie 或任何平台凭据。Linux 上 `safeStorage` 可能退化为 `basic_text`,必须启动时检测并显式降级告警(见 §4.5)。 +4. **Cookie 本地存储 并加密**(Electron `safeStorage`)。服务端**永不存储** Cookie 或任何平台凭据。Linux 上 `safeStorage` 可能退化为 `basic_text`,必须启动时检测并显式降级告警(见 §4.5)。 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。 @@ -182,6 +182,8 @@ type ProxyConfig = { | 豆包 | Doubao | 主动保活 | 8 min + 30 min 探活 | | 千问 | Qwen | 主动保活 | 8 min + 30 min 探活 | | DeepSeek | DeepSeek | 只探活 | 30 min | +| 混元 | Hunyuan(腾讯)| 主动保活(SSE / 短期 token,待接入时复测)| 8 min + 30 min 探活 | +| Kimi | Moonshot | 主动保活(SSE / 短期 token,待接入时复测)| 8 min + 30 min 探活 | 实施细节: @@ -557,6 +559,111 @@ Web UI 可视化 **超时处理**:`lease_expires_at` 到期后 scheduler 把 task 放回 `queued`,`active_attempt_id` 置空;若老 client 恢复后再 POST `/result(lease_token=过期值)`,返 409 → client 本地记为 `unknown` 提示人工。 +### 6.8 admin-web 协同(发布 Modal + 媒体数据面板 + 离线降级) + +桌面客户端是执行者与凭据持有者,但用户日常操作的入口仍然是 `apps/admin-web`(SaaS Web 端)。`/api/desktop/*` 面向桌面端(§6.4),`/api/tenant/*` 面向 Web 端;server 内部共用 §6.3 的 RabbitMQ + DB。 + +#### 6.8.1 角色分工 + +| 角色 | 职责 | 数据主权 | +|---|---|---| +| **admin-web(Web SaaS)** | 内容编辑、创建任务、展示结果、总览媒体数据 | Task 定义与结果的 SoT | +| **desktop-client** | 凭据持有、页面自动化、执行任务、上报结果 | Account existence + cookie 的 SoT | + +#### 6.8.2 发布 Modal 交互流(**账号级**选号,非平台级) + +**核心前提**:一个 workspace 下同一平台通常有**多个账号**(例如 5 个微信公众号 + 3 个头条号 + 2 个知乎号)。Modal 的勾选粒度是**账号**,用户可以挑"平台 A 的账号 1/2/3 + 平台 B 的账号 5"这种跨平台跨账号组合;一个 publish job 展开为 **N 条** `desktop_tasks`,每条一个 `(platform, account)` 组合。 + +流程: + +1. 打开:`GET /api/tenant/accounts?kind=publish&workspace_id=...` 拉回 workspace 下所有发布账号。响应按 `(platform, account)` 两层结构组织,每条带: + - `online`(由 server 根据该账号所属 `client_id` 的 `last_seen_at < 5 min` 判定) + - `health`(`live / expired / captcha / risk`) + - `lastPublishAt`(上次成功发布时间) +2. Modal UI: + - 平台折叠块,标题显示"选中 X / 共 Y" + - 每账号一行:`checkbox · displayName · health badge · online badge · 最近发布时间` + - 全选 / 反选(按平台或全局) + - 选模式(manual / auto)、时间(立刻 / 定时)、素材策略(封面默认 / 指定) +3. 提交:`POST /api/tenant/publish-jobs`,body 示例: + ```json + { + "title": "2026 Q2 新品发布", + "content_ref": "draft:1234", + "accounts": [ + {"account_id": "acc-a1", "mode": "auto"}, + {"account_id": "acc-a2", "mode": "manual"}, + {"account_id": "acc-b5", "mode": "auto"} + ], + "schedule_at": null + } + ``` + server 展开成 3 条 `desktop_tasks(kind=publish)`,共享同一 `job_id`;每条绑定一个 `account_id` 与其所属 `client_id`。 +4. Modal 切到"进度面板",订阅面向 Web 的 SSE `GET /api/tenant/events?topic=job:`;接收每子任务状态变化(`queued → in_progress → waiting_user → succeeded/failed/unknown/aborted`)。 +5. manual 模式任务进入 `waiting_user` 后,面板上显示"账号 X 等待桌面端审核",带"前往桌面端"按钮 → `georankly://tasks//review`(未装客户端时跳下载页)。 +6. 全部终态后 Modal 汇总:每账号结果 URL / 错误码;对失败子任务点"重试" → `POST /api/tenant/publish-jobs/{job_id}/retry?task_ids=...`。 + +#### 6.8.3 媒体数据面板(账号级粒度) + +admin-web 的"账号总览 / 发布历史 / 监控结果"三个页面全部按 `(platform, account)` 两层粒度: + +| 页面 | API | 行粒度 | +|---|---|---| +| **账号总览** | `GET /api/tenant/accounts` | `(platform, platform_uid)` | +| **发布历史** | `GET /api/tenant/publish-jobs?...` | job 分组,展开后每行 `(job, account, status)` | +| **监控结果** | `GET /api/tenant/monitoring-results?plan_id=&range=` | 每行 `(plan, query, llm_account, answer, hit)` | + +统计卡片:workspace 在线桌面端数、各平台账号健康率、近 7 天发布成功率(**按账号**)、近 7 天监控完成率、过去 24h 异常账号数 / 异常平台数。 + +#### 6.8.4 admin-web 新增 / 扩充 API + +``` +GET /api/tenant/accounts # 带 online + health + lastPublishAt 聚合 +GET /api/tenant/accounts/{id} +GET /api/tenant/clients # 本 workspace 的桌面端在线情况 +GET /api/tenant/publish-jobs # 发布批次列表 +GET /api/tenant/publish-jobs/{id} # 批次详情含子任务 +POST /api/tenant/publish-jobs # 创建(见 §6.8.2 step 3) +POST /api/tenant/publish-jobs/{id}/retry # 重跑失败子任务 +GET /api/tenant/monitoring-results # 支持 filter / range / 按 llm / 按账号 +GET /api/tenant/events?topic=job:|account:|workspace # 面向 Web 的 SSE +``` + +**关键**:`/api/tenant/events` 与 `/api/desktop/events`(§6.4)是**两条独立通道**,订阅 topic 不同——前者推 task 状态给 Web UI,后者推 task 分派给 desktop client。 + +#### 6.8.5 离线降级 UX + +- **某桌面端离线**(`last_seen_at > 5 min`): + - Modal 里所属账号标"离线(上次 X 分钟前)",仍可勾选,顶部提示"任务将在该端上线后自动执行"。 + - 账号总览 / 监控结果用灰色 badge。 +- **workspace 所有桌面端都离线**: + - Modal 顶部 banner:"当前 workspace 无在线桌面端。任务会排队等待。[下载桌面端]" + - 监控计划若连续 N 次未执行,站内通知 + 邮件给 workspace owner。 +- **桌面端重新上线**: + - desktop-client 启动 `POST /api/desktop/clients/heartbeat` + §6.8.6 resync;queued 任务通过 SSE 被自然分派。 + - admin-web 的 SSE 收到 `client_online` 事件自动刷新 badge,不需 reload。 + +#### 6.8.6 Account 数据一致性(client ↔ server) + +**client 是 account existence 的 SoT,server 存投影**: + +- 登录完成:client `POST /api/desktop/accounts`(upsert,以 `(workspace_id, platform, platform_uid)` 为键,唯一索引见 §6.6)。 +- 健康 / 昵称变化:client `PATCH /api/desktop/accounts/{id}`。 +- 删除:client `DELETE /api/desktop/accounts/{id}` → server 级联把该账号未完成的 task 置为 `aborted`。 +- **启动时 resync**:client `GET /api/desktop/accounts?client=self` 比对本地清单;server 有而本地没的(用户在别处删过)→ 本地删;反之 upsert;`platform_uid` 冲突走 §6.6 唯一索引处理。 + +admin-web 看到"账号不存在"(Modal 里某账号消失)= client 已删;秒级一致性延迟可接受。 + +#### 6.8.7 深链接 `georankly://` + +| 深链 | 场景 | Fallback | +|---|---|---| +| `georankly://accounts` | "前往桌面端管理账号" | 未装 → 下载页 | +| `georankly://accounts/add?platform=wechat` | "一键添加某平台账号" | 同上 | +| `georankly://tasks//review` | Manual 模式等审核 | 同上 | + +admin-web 点深链前先查 `/api/tenant/clients` 判断是否有该 workspace 的在线桌面端;没有直接弹下载引导,不让用户点空链接。 + --- ## 7. 错误处理与可观测性 @@ -676,7 +783,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un ### 9.3 集成 -用 Playwright `_electron.launch` 驱动 Electron,对 **本地 fake server + 本地 fake 平台**(HTTP server 喂 fixture SSE)。13+3 适配器全跑一遍,并行约 3–5 分钟。 +用 Playwright `_electron.launch` 驱动 Electron,对 **本地 fake server + 本地 fake 平台**(HTTP server 喂 fixture SSE)。13+5 适配器全跑一遍,并行约 3–5 分钟。 ### 9.4 集成 · 协议 @@ -717,7 +824,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un | 阶段 | 受众 | 签名 | 退出门槛 | |---|---|---|---| -| **Alpha(内部)** | 开发 + QA(≤5 人)| Mac 公证 / Win 无签名 / Linux 裸 AppImage | 13+3 适配器 fake + 真实账号成功率 ≥ 90%;lease 协议在混沌测试下无脏写 | +| **Alpha(内部)** | 开发 + QA(≤5 人)| Mac 公证 / Win 无签名 / Linux 裸 AppImage | 13+5 适配器 fake + 真实账号成功率 ≥ 90%;lease 协议在混沌测试下无脏写 | | **Beta(邀请制)** | 公司内部 + 合作客户(≤30 人)| 同上 | 连续 7 天无 P0;保活命中率 > 95%;patch 通道应急修复跑通 ≥ 1 次;autoUpdater smoke 每 release 全过 | | **GA 候选** | 放开注册 | **Win 签名必须到位** | 安装成功率 > 99%;crash rate < 0.1%/session | @@ -741,7 +848,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un | 块 | 范围 | 估时 | |---|---|---| | **A. Desktop runtime 基座** | Main 进程、session-registry、view-pool、vault、lease-manager、rate-limiter、circuit-breaker、crash-recovery、tracing(Alpha 版)、transport (SSE + pull + RabbitMQ presence) | 3–4 周 | -| **B. LLM 监控适配器** | Doubao + Qwen + **DeepSeek(从零)** | 2–3 周 | +| **B. LLM 监控适配器** | Doubao + Qwen + **DeepSeek(从零)** + **混元(从零)** + **Kimi(从零)** | 4–5 周 | | **C. 已有真实发布能力的平台迁移** | 约 6 家(头条 / 百家 / 搜狐 / 知乎 / 企鹅 / 简书)从 chrome.* 迁到 Electron 抽象 | 3 周 | | **D. Detect-only 平台从零实现 publish** | 7 家(微信公众号 / 掘金 / 懂车帝 / 什么值得买 / 网易 / B 站 / ZOL)| 5–7 周 | | **E. 服务端协议 + DB + sqlc 重生 + Service 层重构** | `/api/desktop/*`、租约、presence、patch manifest | 2 周 | @@ -750,7 +857,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un | **H. packages/ui-shared 抽取 + desktop renderer** | — | 1.5 周 | | **I. 测试基建(fake server + fake 平台 + replay)** | — | 1.5 周 | -合计约 **22–26 工程周**。这份 spec 因此**应拆 3 个 plan**(见 §12)。 +合计约 **24–28 工程周**(LLM 从 3 家扩到 5 家增加约 2 周)。这份 spec 因此**应拆 3 个 plan**(见 §12)。 ### 10.4 三层回滚 @@ -803,13 +910,13 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un ### Plan B · 广度铺开(10–12 周,Beta 上限) -- 块 B(Qwen + DeepSeek 从零) +- 块 B(Qwen + DeepSeek + 混元 + Kimi 从零) - 块 C(其余 5 家已有真实发布能力的平台迁移) - 块 D(7 家 detect-only 从零实现 publish) - 块 F 完整:Win/Linux 全架构 + 签名(Win 签名到位)+ autoUpdater 完整 - 块 I 补齐:多适配器回归 + 冒烟 Playbook -**退出目标**:13+3 适配器全量可用,autoUpdater smoke 每 release 全过,5 arch × 3 OS 全量打包签名。 +**退出目标**:13+5 适配器全量可用,autoUpdater smoke 每 release 全过,5 arch × 3 OS 全量打包签名。 ### Plan C · 运维闭环(4–5 周,GA 上限) @@ -824,6 +931,10 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un ## 13. 变更记录 +- **v2.1(2026-04-18)**:用户增量反馈后的小修订。 + - **LLM 监控 3 家 → 5 家**:新增混元(Tencent Hunyuan)、Kimi(Moonshot),§4.4 保活表增行(两家默认走 8 min 主动保活,实际节奏接入时复测);§10.3 块 B 估时 2–3 周 → 4–5 周;总工期 22–26 周 → 24–28 周;§12 Plan B 范围更新。 + - **新增 §6.8「admin-web 协同」**:覆盖发布 Modal 账号级多选语义、媒体数据面板(账号级粒度)、`/api/tenant/*` 聚合与 SSE 通道、离线降级 UX、Account 数据一致性(client = existence SoT / server = 投影)、`georankly://` 深链接。明确 `/api/tenant/events` 与 `/api/desktop/events` 是两条独立 SSE 通道。 + - **发布 Modal 选号粒度固化为账号级**(非平台级):一个 job 展开为 N 条 desktop_tasks,每条绑定一个 `(platform, account)`。 - **v2(2026-04-18)**:Claude + Codex + Gemini 三方交叉审查后的修订版。主要修订: - **C1** 修正 CDP 流式 SSE 抓取方法(§5.2),改为按 EventSource / fetch-stream 分流;维护 Electron Chromium CDP 版本矩阵。 - **C2** Patch 通道默认改为**纯数据 DSL**(无 JS 执行),可执行代码作为备用通道并限定在 `utilityProcess + isolated-vm` 沙箱;引入 TUF 风格 manifest + patch_seq + 根密钥轮换 + 撤销机制(§5.5)。