---
name: lov-media-publisher
description: >-
  通过已登录的创作者后台发布本地视频到微信视频号或 Bilibili，并以媒体预检、描述来源冻结、
  字段回读、封面安全区、终稿确认与列表回读为门禁。Use when users ask to publish, draft,
  schedule, or check a video post, including 发视频号、发 B 站、投稿、上传成片。
license: MIT
compatibility: >-
  只读预检需要 Python 3.8+ 与 ffprobe；全部网页交互走 ego-browser 的 task space
  共享用户已登录态，不调用私有发布 API，也不需要额外平台凭据。
  交接语音可选使用 VOLC_APPID / VOLC_TOKEN 调用火山 TTS，未配置时降级到 macOS say。
depends_on:
  - lov-branding-consistency
metadata:
  author: lovstudio
  version: "0.9.1"
  tags:
    - wechat-channels
    - bilibili
    - video-publishing
    - browser-automation
---

# 发布视频到微信视频号 / Bilibili

只操作平台自己的创作者后台可见网页。把页面当作动态界面：每一步重新读取当前页面，
按语义定位控件，不依赖历史 CSS 选择器、旧句柄或坐标。

## 平台路由

**默认依次发布到微信视频号，再发布到 B 站**，平台队列为
`[wechat-channels, bilibili]`。用户只说「发布」「发出去」且没有指定平台时，直接采用这份
默认队列，不再泛问发布目的地。

- 当前请求明确指定平台时，仅处理指定的平台；明确指定顺序时按该顺序执行。条目链接或
  BV 号已能确定平台的 `status` 请求，只查询该条目，不扩展到默认双平台队列。
- 选择两平台但未另指定顺序时，先完成视频号本次动作并回读列表状态，再进入 B 站流程。
  视频号出现阻塞时先处理并报告具体阻塞，不自行跳过它、提前改发 B 站。
- 素材、账号或必要字段缺失时，先按本 Skill 的输入和预检流程自行查明；只有确实无法解决
  的具体缺项才询问，不把缺项重新解释成平台选择问题。平台限制、DOM 写法和状态分别核验。

| 平台 | `--platform` | 入口 | 结构 | 页面细节 | 平台约束 |
| --- | --- | --- | --- | --- | --- |
| 微信视频号 | `wechat-channels` | `https://channels.weixin.qq.com/platform/post/create` | wujie 微前端 + shadow DOM | [创建页结构](references/wechat-channels/page-anatomy.md) | [视频号约束](references/wechat-channels/platform-constraints.md) |
| Bilibili | `bilibili` | `https://member.bilibili.com/platform/upload/video/frame` | 普通 Vue 应用 | [投稿页结构](references/bilibili/page-anatomy.md) | [B 站约束](references/bilibili/platform-constraints.md) |

四条会反复咬人的平台差异，先记住再动手：

- **可逆性不同**：视频号发布后不可撤改；B 站稿件投出后可 `?type=edit&bvid=` 改标题、
  简介、标签、封面、合集，但**改完必须重新点「立即投稿」**。终稿确认门禁两边都走，
  只是报告口径要跟着变，不要把 B 站的错发渲染成灾难。
- **硬限制差一个数量级**：视频号 4 GiB / 2 小时，B 站 16 GB / 10 小时。拿一边的默认值
  去卡另一边就会得到假结论，所以脚本一律显式传 `--platform`。
- **写值方式完全不同**：视频号的描述是 contenteditable、话题必须由平台按钮生成；
  B 站的输入框要用原生 setter，简介是 Quill 实例。不要互相套用。
- **控件类名一律从 page-anatomy 抄，不许猜**：两边都是自研组件库（视频号 wujie +
  shadow DOM，B 站 `data-v-*` scoped CSS）。套 antd 或通用猜测（`.ant-select-*`、
  `[role="combobox"]`、`.choose-btn`、`.category-item`）一个都命中不了，而且**查不到
  不报错**，表现为「点了没反应」，极易被误读成时机未到而空等几轮。**填任何字段前
  先打开该平台的 page-anatomy 对一遍选择器表**——2026-08-18 EP.03 就是跳过这一步，
  B 站的分区、合集、封面三项全填不进去，最后由用户手填。

### 平台成片映射门禁

平台允许某种画幅，不代表本期就该选那一版。打开发布页前，先从 Creator 交付报告、平台文件名、
Composition 或本期清单确定**本期目标平台对应的已批准成片**：例如本期明确有
`Episode-Horizontal` 与 `Episode-Vertical`，且竖版文件名为 `wechat-channels` 时，B 站应先导出
横版平台 MP4，不能拿竖版“先发了再说”。Studio 里的 Composition 和 `review-ready` 预览都不是
可上传文件；缺少目标平台 MP4 时，状态记为 `blocked-on-platform-variant` 并交回
`lov-media-creator` 渲染、解码和质检。

预检用两层硬门禁：

```bash
python3 $SKILL_DIR/scripts/check_video.py PLATFORM_MP4 \
  --platform bilibili --expected-orientation horizontal --json
```

- 文件名明确含另一平台标识（例如 B 站任务选中 `wechat-channels`）时默认失败；只有用户明确要求
  跨平台复用，才可加 `--allow-cross-platform-name`。
- `--expected-orientation` 来自本期交付契约，不从“B 站一定横版”之类平台刻板规则擅自推断；
  B 站本身支持竖版，但项目已批准横版时传入竖版必须失败。
- 用户可在提交前改选平台文件或明确覆盖画幅；任何覆盖都要重跑预检并写进终稿字段表。

两个平台共用的部分只有：[浏览器工作流](references/browser-workflow.md)（helper 签名、
控制权、任务空间生命周期）、[发布门禁清单](references/publish-gates.md)、
[重压方案](references/encoding-recipes.md)、[视频描述结构](references/description-template.md)。

## Triggers

### Activate when

- 用户要求把本地视频发布到微信视频号或 B 站，或保存成草稿。
- 用户要求安排定时发布、核验发布状态，或 publish a video to Bilibili / video channels。
- 用户要求基于本会话上一版内容重发同一个本地视频。

### Do not activate when

- 用户只要求发布微信公众号文章或调用公众号接口。
- 用户只要求本地媒体预检或转码，不涉及平台页面发布。
- 用户要求剪辑、渲染或生成视频素材本身（那是 `lov-media-creator` 的职责）。

## 读取输入

开始前取得以下输入：

- 必填：按上节解析的**目标平台队列**（未指定时为视频号 → B 站）、目标动作
  `draft` / `schedule` / `publish` / `status`，以及各平台预期账号名称。
- `draft` / `schedule` / `publish` 必填：本地视频路径；`schedule` 还需带时区的发布时间。
- `status` 必填：条目链接、条目 ID / BV 号，或足以唯一定位的文案前缀、文件名和提交时间窗口。
- 可选：文案、话题/标签、封面、位置、合集、原创声明、分区、类型等当前页面支持的设置。

`publish` 还隐含一项**不可省的输入**：提交前用户对终稿的明确同意。会话开头的「发吧」
只授权启动流程，见下节。

把当前会话中用户已确认的主题、标题、描述、话题、封面和原创要求视为本次输入；用户说
「基于上一版」「重新发一遍」或指出某字段漏填时，沿用本会话最后一版已确认的内容，并把
纠正后的要求持续应用到后续步骤。不要补写与主题无关的事实或宣传语。

描述来源按以下优先级解析，命中高优先级后不得再用低优先级覆盖：

1. 用户在当前平台页面手动修改后的实际文本；
2. 用户在当前会话逐字给出的描述；
3. 同一项目中最近一次明确批准的发布文案；
4. 仅在以上均不存在且编辑区为空时，才按 `references/description-template.md` 生成草稿。

用户说“你的描述不行”、已经接管页面修改，或页面文本与 agent 上次写入值不同，都视为
`description_source=user-edited`。此时立即冻结全文：后续封面、合集、原创、位置和话题操作
不得重建描述编辑区，也不得为了“符合模板”润色用户版本。模板是生成草稿的约束，不是覆盖
用户终稿的许可证。

用户进一步给出**实际发布文本**并说明“已经修改并发布”时，这份文本立即成为本期最高优先级
gold reference，同时也是后续平台的表达策略来源：保留它的钩子、语气、大小写和关键词，只做
平台机制所需的最小适配（例如把视频号 `#话题` 迁移到 B 站标签字段、在 B 站钩子后追加批准
章节），不得恢复 agent 旧稿、系列介绍或冗长摘要。把用户报告与平台验证分开记录；未从内容
列表回读前，不把“用户说已发布”写成已验证的 `published`。

知识技巧短视频（通常约 10 分钟以内）没有用户终稿时，默认用“真实痛点问句 → 能力承诺问句
→ 5–10 个精准关键词”，而不是强制“系列第 N 期 + 内容说明 + 客套收尾”。长访谈、长教程或
资源密集内容才启用完整章节与链接骨架。具体判据见 `references/description-template.md`。

系列名默认用**全称**。撞到字数上限时不要直接降级用短名——先分别读创建表单和编辑表单
的 `maxLength`，两者不一定相同（B 站创建弹窗 20 字但编辑表单 50 字，先建短名再改名即可）。
只有两个表单都装不下时才用短名，并在报告里说明。

### 提交前必须由用户确认终稿（`publish` 硬门禁，两平台都适用）

`publish` 不是「字段齐了就发」。所有必填项 `pass` 之后，状态先进 `awaiting_confirmation`，
把终稿交给用户过目，得到明确同意才点主提交按钮。用户说过「发吧」「可以」属于**启动**
授权，不替代**终稿**确认——两者之间发生了上传、平台改写和字段回读，用户当时还没看见
最终结果。

确认动作分三步，缺一步都不算通知到：

1. 一次 `js()` 读完整个字段表（做法见对应平台的 page-anatomy），读真实 `value` /
   `checked` / 标签节点，不用外观推断。
2. 发系统通知 + 语音播报——用户可能不在终端前：

   ```bash
   python3 $SKILL_DIR/scripts/notify_user.py \
     --title "视频号发布 · 终稿待确认" \
     --message "标题/描述/话题/封面已就位，确认后我再提交" \
     --speech "终稿已准备好，请确认后我再发布"
   ```

3. 把字段表连同**平台改写过的地方**一起呈现，然后停下等用户回话。这里与「等控制权」
   不同：控制权是轮询等待，终稿确认必须等到用户真的答复，不设超时、不默认同意、
   不自行放行。

被平台改写的字段要单独标出来，这是用户最需要看见的部分。观测过的例子：视频号短标题
因 16 字上限或禁用符号被改写、封面槽位数与预期不一致、位置被平台自动带入；B 站标签被
「当前tag为话题专用，不允许自定义添加」拒掉后换了同义词、平台按账号名自动塞进
`手工`/`生活记录` 之类的标签。用户只批准过原始文案，没批准过这些改写。

`draft` / `schedule` / `status` 不要求终稿确认：草稿可改，定时在到点前可撤，`status` 只读。

### 发布完整性门禁

对 `publish`，默认要求标题、非空描述、至少一个话题/标签，以及**当前页面实际存在的
每一个封面槽**都有可用内容。用户未逐字给出这些文本时，可从当前会话、同一项目的明确
文案和视频可见主题中整理克制版本；证据不足时停在提交前指出缺项，不发布空白或半成品
内容。`draft` 可保留用户明确允许的未完成字段。

**B 站有已核验章节就必须在简介加入可点击时间轴。** 时间码只能取自最终成片时间轴，格式为
每行一个 `MM:SS 章节名`（超过一小时用 `HH:MM:SS`），不得从源录屏、旧剪辑版或人工记忆
抄写。若项目已有已批准章节而简介缺少时间轴，完整性门禁为 `fail`。对于已经冻结的
`description_source=user-edited`，不得静默覆盖全文：按用户明确要求做最小追加，或停下展示
缺项；修改后重新冻结全文并重跑文案预检、字段回读和终稿确认。

**B 站 tags 要做发布后公开回读。** 投稿表单里的标签节点只能证明提交前状态；发布后额外调用
`/x/tag/archive/tags?bvid=BV...` 读取 `data[].tag_name`，并在公开页核对
`.video-tag-container a.tag-link`。`/x/web-interface/view` 本身不返回 tags，简介里没有
`#标签` 也不代表 tags 丢失。平台会重排标签顺序，所以按精确名称集合比较，不按顺序比较；
接口与公开页都命中预期集合时，不要为了展示误判重新编辑或投稿。

**封面槽位数按页面实测，不写死。** 视频号 2026-08-17 实测创建页只有一个槽（标签
「个人主页和分享卡片(3:4)」），两个预览由同一张 3:4 裁出；B 站有两个**互相独立**的槽
（4:3 首页推荐 / 16:9 个人空间），而列表、空间和信息流用的都是 16:9 那个——只传 4:3
等于没传。读到几个槽就传几张，并在报告里说明未用到的备用件。

**封面存在不等于合格。** 对页面上实际存在的每一个槽逐个打开编辑器，通过截图或效果
预览确认标题主体位于该槽的裁切安全区内。B 站的 16:9 槽尤其不能凭弹窗内预览判定，
唯一可信验证是投稿后拉一次公开接口读 `pic`（做法见
[B 站投稿页结构](references/bilibili/page-anatomy.md)「封面是两个独立的槽」）。
详见 [发布门禁清单](references/publish-gates.md)。

**封面素材有既定来源，不许现场生成。** 每期的封面在 `output/covers/<ep>/`
（`lov-channels-cover` 产出的 `cover_3x4.png` / `cover_4x3.png`）和
`output/deliverables/…-封面-<宽x高>-v*.png` 里，找不到就去问，不要自己造。
那套只产 3:4 和 4:3，**B 站的 16:9 槽是已知缺口**：缺就明说缺，让用户补一张，
绝不用 `ffmpeg -vframes 1` 抽首帧顶替——首帧是片头静帧或正片第一帧，当封面
等于没有封面。2026-08-18 EP.03 我抽了首帧当两个槽的素材，用户手动换成了正式
封面（线上 16:9 实际是 1440×810 的人像+标题版）。

原创（视频号）：**创作者账号默认勾选**，除非用户在同一任务里明确说「不勾原创」「非原创」
等否定词。教训来源（2026-08-21 EP.01）：用户连续多期要求勾原创，agent 一直停在
「用户没明说就不勾」的被动逻辑，每期都要用户提醒。原创是创作者账号的常驻权益，不是
需要用户重申才启用的选项。勾选原创时**必须完成原创权益弹窗中的须知/条款勾选**，并在
弹窗关闭后读取主复选框的真实 `checked=true`，不能只凭视觉样式或点击动作判断。终稿
确认时把「原创：已勾选」列入字段表，让用户看到而不是默认隐藏。

合集：先读取页面实际选中值与现有候选；创建新合集仍需用户明确同意，因为视频号合集创建后
不可改名。用户已在接管期间选择合集，或明确说“合集已勾选并已发布”时，不再询问、不清空、
不重新创建；能回读就记录实际合集名，页面已跳转而名称不可见时只记录 `user-selected`，不得
从本地候选猜名字。教训来源（2026-08-21 EP.01）：agent 把合集当「未提出则默认」的可选项
跳过；2026-08-26 EP.01 则在用户已自行勾选后仍不能继续沿用旧的未选择字段表。

描述/标题等文案：**以用户手改的最终版本为准**。用户修改过描述、标题、话题后，不要
用 agent 自己整理的版本覆盖，也不要在终稿确认时展示旧版。教训来源（2026-08-21 EP.01）：
agent 展示了旧版描述，用户已自行改好。

实现上不能只靠记住这句话：接手或恢复发布页后的**第一个只读动作**是读取描述全文，并记录
`description_source`、精确文本和长度。每次与描述无关的页面写操作后重新读取一次；若与冻结
快照不同，立即停止并恢复前先让用户确认。终稿字段表的 `expected` 取冻结后的用户文本，不能
仍引用本地 `publish-copy.md` 或 agent 旧草稿。用户编辑后不要再调用描述区的清空、`fill`、
`execCommand` 或 Quill `setText()`。

**生成内容标注必须依据素材来源，不能依据视频话题。** 讲解 AI 产品、演示 AI 案例，以及使用
AI 辅助剪辑、字幕或调色，都不能单独证明成片含生成/合成素材。先核对用户对素材来源的明确
说明与可验证的制作证据，不臆测屏幕演示片段的来源；按
[素材来源与生成内容标注](references/publish-gates.md#素材来源与生成内容标注)记录依据并回读字段。

广告、可见范围、位置、分区、类型和评论属于会改变发布语义的选项：用户明确提出
时执行，未提出时保持平台默认。位置若由平台自动带入，也要在提交前回读并列入字段表。

如果用户只说「我的号」，且页面仅展示一个清晰的已登录账号名称，记录该名称并在提交前
回报；出现账号选择器、多个账号或模糊头像时先确认。若用户要求的标题、描述、话题或原创
声明在页面回读中缺失，停在提交前，指出具体缺失字段，不点击发布。

### 发布协作节奏（硬约束）

- **开页前冻结素材清单**：先把平台成片和所有封面解析为存在、可读的绝对路径，
  并记录版本、尺寸和平台槽位。不能只把路径藏在文件输入或工具日志里。
- **只按依赖等待**：视频输入一旦开始上传，标题、简介、标签、合集、原创和其他
  已挂载的独立字段立即进入写入/回读通道。只有封面编辑器、视频解析结果和提交动作
  等真实依赖继续等待；不得为了轮询上传进度把全表串行化。
- **人工交接必须可执行**：只有登录、平台阻断或真实遮挡无法自动化时才交接。交接同时：
  1. 在对话中给出可点击的成片与封面绝对路径；
  2. 系统通知写清平台、当前阻塞和唯一需要用户完成的动作；
  3. 优先用火山 TTS 播报同一条指令，并明说「不要点发布」或「可以点发布」的边界；
  4. 交出控制权后只等待归还，不再读页、点击或自行接管。
- **失败立即降级**：自动上传或封面写入遇到确定阻塞后，不重复同一无效动作。
  先保留已填字段，再交付精确文件路径和单一人工步骤；用户操作后只回读变更区域。

## 执行流程

1. 对 `draft` / `schedule` / `publish`，先读该平台的 platform-constraints，再运行只读视频预检
   （**必须带 `--platform`**，两个平台的硬限制差一个数量级）：

   ```bash
   python3 $SKILL_DIR/scripts/check_video.py <视频路径> --platform wechat-channels --json
   python3 $SKILL_DIR/scripts/check_video.py <视频路径> --platform bilibili \
     --expected-orientation "$EXPECTED_ORIENTATION" --json
   ```

   `EXPECTED_ORIENTATION` 必须先从本期交付契约读取并设为 `horizontal` / `vertical` / `square`；
   没有项目证据时不得仅凭平台名称猜值。

   硬错误出现时停在预检阶段并回报；警告不阻断，但需在上传前明确列出。若页面明确显示
   账号已放宽（视频号 20 GiB / 8 小时），记录页面原文后以 `--max-gb` / `--max-hours` 覆盖重跑。
   实时页面限制更低时服从更低限制。`status` 是只读查询，跳过视频预检与上传。

   长视频（约 30 分钟以上）或大文件例外：此时把 `warnings` 当作实际门槛，先按
   [重压方案](references/encoding-recipes.md) 消除偏离项再上传，尤其是 `video_bitrate_high`。
   平台的转码拒绝只在整包传完、服务端解析后才返回，长视频一次失败就是数小时代价。

2. **同一时刻把文案也预检掉**，不要等页面来拒。这些全是纯字符串判定，撞一次就多一个
   「填入 → 失焦 → 读校验 → 改写」的页面往返：

   ```bash
   # 视频号：短标题 16 字上限 + 禁用逗号 + 合集 10 字且创建后不可改
   python3 $SKILL_DIR/scripts/check_copy.py --platform wechat-channels \
     --short-title "第一时间读 Harness 架构" --description "$(cat 描述.txt)" \
     --topic DeepSeek --collection "学 Harness" --json

   # B 站：标题 80 / 简介 2000 / 标签 ≤10 且部分名字是话题专用
   python3 $SKILL_DIR/scripts/check_copy.py --platform bilibili \
     --title "如何快速上手一个新项目" --description "$(cat 简介.txt)" \
     --topic 架构设计 --topic 开源 \
     --collection "手工川与你一起学 DeepSeek Harness" --collection-stage edit --json
   ```

   `status: fail` 时先改文案再开浏览器。它只覆盖能离线判定的部分，**通过不等于平台一定
   接受**，仍要在填入后读一次校验提示与 toast。两条要看清：视频号合集限 10 字且创建后
   不可修改，超长时停下来重新起名而不是截断照建；B 站的话题专用标签名单只覆盖已实测被
   拒的名字，新名字仍会在页面上被拒。

3. 读 [浏览器工作流](references/browser-workflow.md)，按 `ego-browser` 任务空间流程打开该
   平台入口：

   ```bash
   ego-browser nodejs <<'EOF'
   const task = await useOrCreateTaskSpace('media publish')
   await openOrReuseTab(ENTRY_URL, { wait: true })
   await snapshotText()
   cliLog('task space: ' + task.id)
   EOF
   ```

   helper 一律**传 id 不传 task 对象**（`handOffTaskSpace(task)` 会抛
   `task space not found: [object Object]`），完整签名坑表在同一份文档里。

4. 先 `snapshotText()` 再定位账号、账号状态与页面可见控件；每次关键操作后回读快照。
5. 若出现登录二维码或账号选择，`handOffTaskSpace(task.id)` 交给用户，待用户返回后再续接，
   不代填凭据、也不绕过登录。视频号本机微信已登录时优先点「快捷登录」，不要为了扫码
   多做一次交接。交出控制权后只能通过 `waitForAgentControl(task.id)` 等待归还，**任何情况
   下都不得自行 `takeOverTaskSpace()`**——包括只想读一次快照、或页面看起来已经就绪时。
   交出时先展示素材清单，再发系统通知并语音播报
   （`$SKILL_DIR/scripts/notify_user.py --tts-provider auto`），然后在后台轮询控制权状态，不要停下来
   要求用户回话。用户明确说「已登录」「好了」之后才接手。
6. 读取页面展示的账号名并与预期逐字核对。只有一个清晰账号且用户说「我的号」时记录该
   名称；账号不符、多个候选或只有模糊头像时停下确认。
7. 汇总本会话已确认的字段，形成提交前字段表，每项记录
   `required / source / expected / actual / result`。接手页面先读取描述；页面已有非空描述且
   与 agent 上次写入值不同，`source=user-edited`，当场冻结，不进入描述写入分支。
   通用项：账号、标题、描述、话题/标签、封面（按页面实际槽位数逐槽一行）、合集、定时。
   视频号加：短标题、位置、原创、视频标注、评论、可见范围。B 站加：分区、类型、
   已核验章节的可点击时间轴、16:9 封面的 `pic` 回读结果。字段表全文见
   [发布门禁清单](references/publish-gates.md)。
   账号、目标动作及所需输入均明确后，把状态记为 `prepared`。
8. `status` 直接打开内容管理列表并跳到第 12 步。其他动作通过**主上传区**关联的文件输入
   提交原视频——两个平台都存在多个 `accept` 相同的 file input，必须按平台文档里的判据
   锚定，不能取第一个。观察到上传进度后记为 `uploading`；进度 100% 后仍等待转码、缩略图
   或素材解析，记为 `processing`。上传开始后，只要字段区已经挂载且连续两次回读稳定，就并行
   填写不依赖素材解析的标题、描述、话题、合集、原创和标注；不要为了等待上传完成把这些字段
   串行化。每次写入仍须遵守描述冻结规则。素材解析完成后统一重读一次，发现平台重渲染清空时
   才做最小补写。
9. 素材解析完成且页面未显示视频错误后，填写封面等依赖素材的字段，并完成全表回读。写入前先按
   来源优先级判断每个字段；`description_source=user-edited` 时跳过描述写入，只做只读回读。按各平台 page-anatomy
   的写值方式操作，并逐项回读实际值：
   - **视频号**：短标题读输入框 `value`，平台报特殊字符时按提示做最小修正并确认警告消失；
     描述读编辑区实际文本；话题必须读平台已识别的独立标签节点，不只检查原始 `#文本`；
     封面的图片文件输入只在编辑器打开后才存在；原创要生效需先勾须知再读主复选框属性。
     合集默认询问用户归属哪个合集（创作者按系列发内容时尤其如此）；原创默认勾选并
     完成须知弹窗确认（见上文「原创」段），不要等用户重申。
   - **B 站**：输入框用原生 setter 写值，`inputType` 不能省；写完的计数器**必须下一轮
     `js()` 再读**（同一轮读到的是 `nextTick` 前的旧值，`0/20` 是假失败）；简介用
     Quill 用 `.ql-container.__quill.setText(text, 'user')` 后 `blur()`，下一轮从 `.ql-editor`
     或 `getText()` 逐字回读；填标签前先清掉平台自动猜的那几个；`svg.close` 与
     `.hot-tag-item` 只吃 CDP 真鼠标。项目存在已核验章节时，从最终成片时间轴生成完整的
     `MM:SS 章节名` 列表并写进简介；下一轮逐条回读全部时间码和标题，缺一条都不能提交。
     封面右侧「首页推荐 / 个人空间」按钮只切换效果预览；真正上传槽是包住 `#editor_4_3` /
     `#editor_16_9` 的 canvas wrapper，必须逐槽回读 `active`、上传并截图，且关闭双比例同步。
   每个与描述无关的写操作后都要断言冻结描述逐字未变。任何必填字段未持久化、用户描述被改动
   或校验仍有警告时，停在提交前。
10. **`publish` 在此停下等用户确认终稿**：一次 `js()` 读完整个字段表，跑 `notify_user.py`
    发通知与播报，把终稿（尤其被平台改写过的字段）交给用户过目，状态记为
    `awaiting_confirmation`。得到明确同意才继续。`draft` / `schedule` / `status` 跳过本步。
    如果用户接管页面后返回并说“我已改好并发布”，不要把这句话当成让 agent 再点一次的确认：
    立即冻结用户给出的实际文本，停止所有表单写入，直接进入内容列表只读回读；回读前保留上个
    已证实状态，并另记 `user_reported_published=true`。
11. 按目标动作进入对应分支：
    - `draft`：选「保存草稿」语义动作，确认完成后进入草稿列表并重载。
    - `schedule`：启用定时发布，核对页面时区、日期和时间，确认提交后进入内容列表并重载。
    - `publish`：所有必填项 `pass` 且用户已确认终稿后，点主提交动作（视频号「发表」/
      B 站「立即投稿」），**只提交一次**，再进入内容列表并重载。未得到确认时不要点击，
      也不要因为等待而改用 `draft` 绕过。主提交按钮常落在视口外（视频号观测 y=726 / 视口高
      727，B 站观测 y=1380 / 视口高 731），坐标点击会静默丢；先 `scrollIntoView` 再**按新
      rect** 操作，点完回读页面状态确认真的生效。B 站先做一次语义/直接点击；仅在确认真实遮挡且
      没有任何提交态时交用户操作，禁止隐藏遮挡层或盲点第二次。
    - `status`：不修改条目；打开与目标标识匹配的内容列表并重载，读取平台状态原文。
12. 用账号及当前可用的条目 ID / BV 号、链接、视频文件名、文案前缀、提交时间、封面等组合
    指纹查找唯一条目。记录列表状态、条目链接或 ID、回读时间与页面 URL。B 站的管理页拒绝
    直链（`upload-manager/all` 与 `/season` 会跳回首页），要从侧边栏点进去。B 站还要分别
    回读 `view.data.title`、`view.data.desc`、`view.data.pic` 与
    `x/tag/archive/tags.data[].tag_name`；前三项逐字/目视核对冻结终稿，公开 tags 按集合验收，
    不能从简介里是否出现 `#` 推断。公开字段缺失时只编辑同一 BV 并重投一次，不创建重复稿件。

## 状态契约

状态只使用下列值；出错时保留最后一个已确认状态，把错误另列：

| 状态 | 成立证据 |
| --- | --- |
| `prepared` | 平台与账号已核对，动作所需输入完成；写入动作也已通过只读预检 |
| `uploading` | 页面出现本次文件的实际上传进度 |
| `processing` | 上传完成，平台仍在转码、解析或生成封面 |
| `awaiting_confirmation` | 仅 `publish`：字段表全项 `pass`，已通知用户并把终稿交他过目，等待明确同意 |
| `draft_saved` | 草稿列表重载后找到唯一匹配条目 |
| `scheduled` | 内容列表重载后找到匹配条目和正确定时时间/状态 |
| `platform_pending` | 内容列表重载后找到唯一条目，状态明确为转码、审核或发布处理中 |
| `published` | 内容列表重载后找到匹配条目，且列表明确显示已发布/已发表状态 |
| `publish_failed` | 内容列表重载后找到唯一条目，且列表明确显示失败、拒绝或终止状态 |

成功提示、按钮消失、请求返回或页面跳转都不单独证明 `published`。没有列表重载回读时，
停留在最后一个已证实状态。B 站还必须用公开 `view` 的 `title / desc / pic` 证明终稿与 16:9
封面真的落库，不要用表单内预览或成功页代替。

## 收口错误

- 提交后超时或状态不明时，不要再次点击发布；先去内容列表重载回读，防止重复内容。
- 主提交按钮进入加载态、页面跳转或出现成功提示后，均视为已经尝试提交；后续只做列表回读。
- 上传或处理失败时记录页面原文、文件名、预检摘要、账号、当前 URL、最后状态与发生时间；
  保留源视频不变。
- 页面显示转码失败（如「视频转码失败，调整视频导出参数后重试」）时，这是服务端拒绝，
  不是网络问题：重传同一文件只会再失败一次。先核对文件大小与时长确实在页面显示的限制内，
  再按 [重压方案](references/encoding-recipes.md) 消除预检偏离项、重压并重新验证。
- 重发同一素材时列表里会同时存在旧条目与新条目，且文件名、时长、首帧完全相同。此时禁止
  用这些字段做指纹，改用提交时间、标题、话题组合、新封面或条目 ID / BV 号；无法区分时保留
  最后确认状态并报告全部候选。旧条目是否删除由用户决定。
- 页面结构变化时重新获取快照，以角色、可见名称、标签和邻近文案定位；多个候选时停止点击
  并报告候选。
- **控件查不到不代表控件不存在**：视频号的表单在 `<wujie-app>` 的 shadow root 里，
  `document.querySelector` 一律返回空且不报错，容易被读成「还没渲染完」而白等几轮；文件输入
  连 shadow root 都查不到，只有 `DOM.getDocument({ pierce: true })` 能看见。B 站没有这个问题，
  但换成了「Vue 不吃合成事件」。各自的定位表与可复用探针在两份 page-anatomy 里。
- **视频号的弹窗是预渲染的，按可见性过滤而非存在性**：约 35 个弹窗全部在 DOM 里，只靠尺寸
  隐藏。读 `.weui-desktop-dialog` 的 `innerText` 会拿到与当前状态无关的文案（观测实例：上传
  封面后读到「将此次编辑保留?」，屏幕上并无此弹窗），据此点按钮就是点空。用
  `getBoundingClientRect().width > 100` 过滤。
- **点了没反应且没报错**时不要重试同样的点法：先判断是落在视口外（滚进来再按新 rect 点），
  还是控件只吃真鼠标（用 `cdp('Input.dispatchMouseEvent', ...)`）。观测实例：B 站报告删了 12 个
  标签，实际一个没少。
- 列表若显示「原创审核中」「审核中」「转码中」等处理中原文，状态用 `platform_pending`，即使
  已出现成功提示或已跳转列表也不要写成 `published`。
- 用户未答复终稿确认时，`awaiting_confirmation` 就是本次任务的最终状态，照实回报——这不是
  失败，也不要为了收尾而改走 `draft` 或自行放行。此时保留任务空间与页面。
- 用户报告自己已修改并发布时，禁止再次点击主提交按钮，也禁止用旧字段表覆盖页面；后续只做
  内容列表回读。列表尚未回读时说明“用户报告已发布、平台状态未验证”，不要误报 `published`。
- 用户在任务任意阶段明确要求「发布后不要关闭」「保留页面 review」时，该要求持续生效直到
  明确撤销。收尾必须使用 `completeTaskSpace(task.id, { keep: true })` 并检查返回的 `done`。
- 最终回报平台、目标动作、账号、最后状态、平台状态原文、列表回读证据、警告和未完成项。
  只有满足状态契约时才使用 `draft_saved` / `scheduled` / `platform_pending` / `published` /
  `publish_failed`。B 站的报告要额外说明稿件仍可编辑，视频号的不要这么说。

与相邻 Skill 的分工与不做什么，见 [组合决策](references/skill-composition.md)。
