docs(desktop-client): spec v2.1 adds admin-web coordination + 2 LLMs

User feedback integrated:

- LLM monitoring scope: 3 → 5 providers (add Hunyuan + Kimi).
  Keep-alive table in §4.4 gets 2 rows (default 8 min heartbeat,
  re-evaluate on integration). Block B effort 2-3w → 4-5w; total
  22-26w → 24-28w; Plan B scope updated.
- New §6.8 "admin-web coordination" covering:
  - Publish modal with *account-level* selection (not platform-level):
    one workspace has many accounts per platform (e.g., 5 WeChat);
    one job expands to N desktop_tasks each bound to (platform, account).
  - Media data dashboards at account-level granularity (overview,
    publish history, monitoring results).
  - New /api/tenant/* aggregation + SSE event bus (separate channel
    from /api/desktop/events).
  - Offline degradation UX when client(s) are offline.
  - Account data consistency: client is SoT for account existence,
    server stores projection; startup resync on upsert/delete.
  - georankly:// deep links with installed-client detection fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 21:22:38 +08:00
parent 2e1a5900ea
commit c41222706b
@@ -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-webWeb 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:<job_id>`;接收每子任务状态变化(`queued → in_progress → waiting_user → succeeded/failed/unknown/aborted`)。
5. manual 模式任务进入 `waiting_user` 后,面板上显示"账号 X 等待桌面端审核",带"前往桌面端"按钮 → `georankly://tasks/<id>/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:<id>|account:<id>|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 resyncqueued 任务通过 SSE 被自然分派。
- admin-web 的 SSE 收到 `client_online` 事件自动刷新 badge,不需 reload。
#### 6.8.6 Account 数据一致性(client ↔ server
**client 是 account existence 的 SoTserver 存投影**
- 登录完成: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/<id>/review` | Manual 模式等审核 | 同上 |
admin-web 点深链前先查 `/api/tenant/clients` 判断是否有该 workspace 的在线桌面端;没有直接弹下载引导,不让用户点空链接。
---
## 7. 错误处理与可观测性
@@ -676,7 +783,7 @@ GitHub Actionsrunner 矩阵:`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 Actionsrunner 矩阵:`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 Actionsrunner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
| 块 | 范围 | 估时 |
|---|---|---|
| **A. Desktop runtime 基座** | Main 进程、session-registry、view-pool、vault、lease-manager、rate-limiter、circuit-breaker、crash-recovery、tracingAlpha 版)、transport (SSE + pull + RabbitMQ presence) | 34 周 |
| **B. LLM 监控适配器** | Doubao + Qwen + **DeepSeek(从零)** | 23 周 |
| **B. LLM 监控适配器** | Doubao + Qwen + **DeepSeek(从零)** + **混元(从零)** + **Kimi(从零)** | 45 周 |
| **C. 已有真实发布能力的平台迁移** | 约 6 家(头条 / 百家 / 搜狐 / 知乎 / 企鹅 / 简书)从 chrome.* 迁到 Electron 抽象 | 3 周 |
| **D. Detect-only 平台从零实现 publish** | 7 家(微信公众号 / 掘金 / 懂车帝 / 什么值得买 / 网易 / B 站 / ZOL| 57 周 |
| **E. 服务端协议 + DB + sqlc 重生 + Service 层重构** | `/api/desktop/*`、租约、presence、patch manifest | 2 周 |
@@ -750,7 +857,7 @@ GitHub Actionsrunner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
| **H. packages/ui-shared 抽取 + desktop renderer** | — | 1.5 周 |
| **I. 测试基建(fake server + fake 平台 + replay** | — | 1.5 周 |
合计约 **2226 工程周**。这份 spec 因此**应拆 3 个 plan**(见 §12)。
合计约 **2428 工程周**(LLM 从 3 家扩到 5 家增加约 2 周)。这份 spec 因此**应拆 3 个 plan**(见 §12)。
### 10.4 三层回滚
@@ -803,13 +910,13 @@ GitHub Actionsrunner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
### Plan B · 广度铺开(1012 周,Beta 上限)
- 块 BQwen + DeepSeek 从零)
- 块 BQwen + DeepSeek + 混元 + Kimi 从零)
- 块 C(其余 5 家已有真实发布能力的平台迁移)
- 块 D7 家 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 · 运维闭环(45 周,GA 上限)
@@ -824,6 +931,10 @@ GitHub Actionsrunner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
## 13. 变更记录
- **v2.12026-04-18**:用户增量反馈后的小修订。
- **LLM 监控 3 家 → 5 家**:新增混元(Tencent Hunyuan)、KimiMoonshot),§4.4 保活表增行(两家默认走 8 min 主动保活,实际节奏接入时复测);§10.3 块 B 估时 2–3 周 → 4–5 周;总工期 22–26 周 → 2428 周;§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)`。
- **v22026-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)。