界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
效率与协作 · SKILL.md

lov-media-publisher

lov-media-publisher

【效率与协作】通过已登录的创作者后台发布本地视频到微信视频号、Bilibili 或小红书,并以媒体预检、描述来源冻结、 字段回读、封面安全区、终稿确认与列表回读为门禁。Use when users ask to publish, draft, schedule, or check a video post, including 发视频号、发 B 站、发小红书、投稿、上传成片。

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

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.10.0" tags:
    • wechat-channels
    • bilibili
    • xiaohongshu
    • video-publishing
    • browser-automation

视频分发助手 · Video Publisher

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

平台路由

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

  • 当前请求明确指定平台时,仅处理指定的平台;明确指定顺序时按该顺序执行。条目链接或 BV 号已能确定平台的 status 请求,只查询该条目,不扩展到默认双平台队列。
  • 选择两平台但未另指定顺序时,先完成视频号本次动作并回读列表状态,再进入 B 站流程。 视频号出现阻塞时先处理并报告具体阻塞,不自行跳过它、提前改发 B 站。
  • 素材、账号或必要字段缺失时,先按本 Skill 的输入和预检流程自行查明;只有确实无法解决 的具体缺项才询问,不把缺项重新解释成平台选择问题。平台限制、DOM 写法和状态分别核验。
  • 小红书不在默认队列里:只说「发小红书」时队列为 [xiaohongshu];「发布,也发小红书」时 默认队列后接小红书;用户给了顺序就按用户的。同期内容继承已冻结终稿的表达核心,按平台做最小 适配(标题 ≤20 字重派生、正文去掉行内 #话题、话题走页面候选),改写处列入终稿确认;成片仍按 下文「平台成片映射门禁」取小红书对应文件,用户明确同意复用别的平台文件才加 --allow-cross-platform-name。
平台--platform入口结构页面细节平台约束
微信视频号wechat-channelshttps://channels.weixin.qq.com/platform/post/createwujie 微前端 + shadow DOM创建页结构视频号约束
Bilibilibilibilihttps://member.bilibili.com/platform/upload/video/frame普通 Vue 应用投稿页结构B 站约束
小红书xiaohongshuhttps://creator.xiaohongshu.com/publish/publish?source=officialVue + tiptap 正文 + 封闭 shadow 的发布按钮发布页结构小红书约束

几条会反复咬人的平台差异,先记住再动手:

  • 可逆性不同:视频号发布后不可撤改;B 站稿件投出后可 ?type=edit&bvid= 改标题、 简介、标签、封面、合集,但改完必须重新点「立即投稿」。终稿确认门禁三个平台都走, 只是报告口径要跟着变,不要把 B 站的错发渲染成灾难。小红书笔记发布后能否改视频、标题、 正文尚未实测,在证实之前按视频号的不可逆口径处理与报告。
  • 硬限制差一个数量级:视频号 4 GiB / 2 小时,B 站 16 GB / 10 小时。拿一边的默认值 去卡另一边就会得到假结论,所以脚本一律显式传 --platform。码率和容器同理:≤ 10 Mbps、 MP4 是视频号的建议;B 站没有码率上限,推荐 MP4 / MOV / MKV,且会重新转码每条投稿, 直接传渲染母版。小红书上传页写的是「4 小时以内、最大 20GB、推荐 mp4、mov」,同样没有 码率要求,直接传已批准成片。
  • 写值方式完全不同:视频号的描述是 contenteditable、话题必须由平台按钮生成; B 站的输入框要用原生 setter,简介是 Quill 实例;小红书标题用真点击 + Input.insertText, 正文是 tiptap / ProseMirror,话题必须从 # 候选里按名称全等点选成 a.tiptap-topic 节点。 不要互相套用。
  • 小红书的发布按钮读不到文字:「暂存离开」「发布」都在封闭 shadow root 的自定义元素 XHS-PUBLISH-BTN 里,按文字查找一律落空。点击前现拍截图并核对视口尺寸,断言 elementFromPoint(x, y).tagName === 'XHS-PUBLISH-BTN' 后只点一次;这个断言分不清「暂存离开」 与「发布」,左右只能看刚拍的截图。
  • 控件类名一律从 page-anatomy 抄,不许猜:几个平台都是自研组件库(视频号 wujie + shadow DOM,B 站 data-v-* scoped CSS,小红书 d-* 组件 + tiptap)。套 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 渲染、解码和质检。

预检用两层硬门禁:

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 站本身支持竖版,但项目已批准横版时传入竖版必须失败。
  • 用户可在提交前改选平台文件或明确覆盖画幅;任何覆盖都要重跑预检并写进终稿字段表。

各平台共用的部分只有:浏览器工作流(helper 签名、 控制权、任务空间生命周期)、发布门禁清单、 重压方案、视频描述结构。

Triggers

Activate when

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

Do not activate when

  • 用户只要求发布微信公众号文章或调用公众号接口。
  • 用户只要求本地媒体预检或转码,不涉及平台页面发布。
  • 用户要求剪辑、渲染或生成视频素材本身(那是 lov-media-creator 的职责)。
  • 用户要做小红书图文笔记或图文卡片(卡片生成用 baoyu-xhs-images)。

读取输入

开始前取得以下输入:

  • 必填:按上节解析的目标平台队列(未指定时为视频号 → 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. 发系统通知 + 语音播报——用户可能不在终端前:

    python3 $SKILL_DIR/scripts/notify_user.py \
      --title "视频号发布 · 终稿待确认" \
      --message "标题/描述/话题/封面已就位,确认后我再提交" \
      --speech "终稿已准备好,请确认后我再发布"
    
  3. 把字段表连同平台改写过的地方一起呈现,然后停下等用户回话。这里与「等控制权」 不同:控制权是轮询等待,终稿确认必须等到用户真的答复,不设超时、不默认同意、 不自行放行。

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

draft / schedule / status 不要求终稿确认:草稿可改,定时在到点前可撤,status 只读。 例外:小红书的「暂存离开」与「发布」同在一个封闭 shadow 宿主里,草稿和定时流程也都未实测, 在证实前小红书的 draft / schedule 同样先走终稿确认,或把那一下交给用户点。

发布完整性门禁

对 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 等于没传。小红书只有「设置封面」一处,默认取视频第一帧;悬停才出现的「编辑封面」入口 2026-10-01 两次 CDP 点击都没打开编辑器,上传路径尚未实测——第一帧就是批准封面时 可接受默认值,否则把封面文件绝对路径交给用户手动上传,不要猜点法。读到几个槽就传几张, 并在报告里说明未用到的备用件。

封面存在不等于合格。 小红书编辑器打不开,改用「封面预览」现拍截图确认标题完整。其余平台 对页面上实际存在的每一个槽逐个打开编辑器,通过截图或效果 预览确认标题主体位于该槽的裁切安全区内。B 站的 16:9 槽尤其不能凭弹窗内预览判定, 唯一可信验证是投稿后拉一次公开接口读 pic(做法见 B 站投稿页结构「封面是两个独立的槽」)。 详见 发布门禁清单。

封面素材有既定来源,不许现场生成。 每期的封面在 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,不能只凭视觉样式或点击动作判断。终稿 确认时把「原创:已勾选」列入字段表,让用户看到而不是默认隐藏。B 站没有独立原创控件, 用推荐标签里的「原创」chip;小红书是「原创声明」开关 + 须知弹窗两步(勾「我已阅读并同意」 再点「声明原创」),同样默认开启并回读开关的 checked 状态。

合集:先读取页面实际选中值与现有候选;创建新合集仍需用户明确同意,因为视频号合集创建后 不可改名。用户已在接管期间选择合集,或明确说“合集已勾选并已发布”时,不再询问、不清空、 不重新创建;能回读就记录实际合集名,页面已跳转而名称不可见时只记录 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()。

生成内容标注默认不勾。 视频号「视频标注」、B 站「创作声明」、小红书「内容类型声明」 里的「含AI生成内容」类选项,按作者 2026-10-01 的常驻要求原则上不选,保持「无需标注 / 内容无需标注 / 未添加」,只有用户在当次任务里明确要求标注时才选。来源证据显示含生成素材 (TTS 旁白、AI 配乐或封面等)时,在终稿确认里写明「含生成素材,按偏好未标注」,由用户当次决定。平台自身强制添加的标识不得移除;话题与素材来源的分开判断、依据记录 与字段回读仍按 素材来源与生成内容标注 执行。注意 B 站创作声明「添加后不可再次编辑」且会在播放时展示,选错代价高。

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

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

发布协作节奏(硬约束)

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

执行流程

  1. 对 draft / schedule / publish,先读该平台的 platform-constraints,再运行只读视频预检 (必须带 --platform,各平台的硬限制差一个数量级):

    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
    python3 $SKILL_DIR/scripts/check_video.py <视频路径> --platform xiaohongshu \
      --expected-orientation "$EXPECTED_ORIENTATION" --json
    

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

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

    视频号的长视频(约 30 分钟以上)或大文件例外:此时把 warnings 当作实际门槛,先按 重压方案 消除偏离项再上传,尤其是 video_bitrate_high。 视频号的转码拒绝只在整包传完、服务端解析后才返回,长视频一次失败就是数小时代价。

    B 站不适用这条例外:投稿页只限大小与时长,没有码率上限,且会重新转码每条投稿。预检对 B 站只在 notes 里输出 video_bitrate_no_platform_cap,不报码率警告。直接上传 B 站的 已批准平台成片(渲染母版),不要为码率、体积或容器(MOV / MKV 转 MP4)重压或重新封装; 同一期视频号用了重压版时,B 站仍传母版。只有 B 站页面真的返回转码失败时,才按下文 「收口错误」进入重压方案。

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

    # 视频号:短标题 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
    
    # 小红书:标题 20 / 正文 1000;话题能否命中已有话题只能在页面候选里看
    python3 $SKILL_DIR/scripts/check_copy.py --platform xiaohongshu \
      --title "国庆72小时 玩转南头古城" --description "$(cat 正文.txt)" \
      --topic 南头古城 --topic 深圳 --json
    

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

  3. 读 浏览器工作流,按 ego-browser 任务空间流程打开该 平台入口:

    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 站加:分区、创作声明、原创 chip、 已核验章节的可点击时间轴、16:9 封面的 pic 回读结果。小红书加:原创声明、内容类型声明、 地点、可见范围、「封面预览」截图结论。字段表全文见 发布门禁清单。 账号、目标动作及所需输入均明确后,把状态记为 prepared。

  8. status 直接打开内容管理列表并跳到第 12 步。其他动作通过主上传区关联的文件输入 提交原视频——视频号和 B 站都存在多个 accept 相同的 file input,必须按平台文档里的判据 锚定,不能取第一个;小红书上传页只有一个 input.upload-input,仍要断言唯一后再提交。 小红书提交视频后可能立刻弹出浏览器定位权限请求,ego-browser 会自动把控制权交给用户: 这是硬停,请用户处理(默认建议拒绝,地点可以按名称搜索),等用户说继续再接手。观察到上传进度后记为 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、上传并截图,且关闭双比例同步。 改完标题后重读分区——平台会按新标题重新猜分区(观测:游戏 → vlog)。bcc-select 下拉 有展开动画,动画中读到的选项 rect 会重叠,等动画结束再读并用 elementFromPoint 确认命中。
    • 小红书:标题真点击 + Input.insertText,≥1 秒后复读;正文写进 .tiptap.ProseMirror;话题 只点 # 候选里名称全等的项,按 a.tiptap-topic[data-topic] 的 name 集合验收,「新建话题」列为 改写;原创声明走开关 + 须知弹窗两步;地点按名称搜索、只选全等项;只统计可见的错误节点。 片段与边界条件见 小红书发布页结构。 每个与描述无关的写操作后都要断言冻结描述逐字未变。任何必填字段未持久化、用户描述被改动 或校验仍有警告时,停在提交前。
  10. publish 在此停下等用户确认终稿:一次 js() 读完整个字段表,跑 notify_user.py 发通知与播报,把终稿(尤其被平台改写过的字段)交给用户过目,状态记为 awaiting_confirmation。得到明确同意才继续。draft / schedule / status 跳过本步。 如果用户接管页面后返回并说“我已改好并发布”,不要把这句话当成让 agent 再点一次的确认: 立即冻结用户给出的实际文本,停止所有表单写入,直接进入内容列表只读回读;回读前保留上个 已证实状态,并另记 user_reported_published=true。

  11. 按目标动作进入对应分支:

    • draft:选「保存草稿」语义动作,确认完成后进入草稿列表并重载。小红书的「暂存离开」与 「发布」同宿主且草稿位置未实测,按上文例外先确认终稿或交用户点。
    • schedule:启用定时发布,核对页面时区、日期和时间,确认提交后进入内容列表并重载。
    • publish:所有必填项 pass 且用户已确认终稿后,点主提交动作(视频号「发表」/ B 站「立即投稿」/ 小红书「发布」),只提交一次,再进入内容列表并重载。小红书的按钮在 封闭 shadow 的 XHS-PUBLISH-BTN 里,按最新截图坐标 + elementFromPoint 标签名断言后真鼠标点一次; 跳到 /publish/success 只说明已尝试提交。未得到确认时不要点击, 也不要因为等待而改用 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 并重投一次,不创建重复稿件。 B 站稿件「审核中」时公开 view 接口返回 -404,属正常:先用创作中心 member.bilibili.com/x/web/archives(带登录态)读 bvid / state_desc / cover / tag,并下载 cover 目视核对 16:9;标签接口审核期间已可回读;公开 title / desc / pic 留到过审后补读。 小红书在 https://creator.xiaohongshu.com/new/note-manager 重载后按标题、时长、发布时间找 唯一条目:「审核中」→ platform_pending,「未通过」→ publish_failed,只有在「已发布」 分页里出现才算 published。

状态契约

状态只使用下列值;出错时保留最后一个已确认状态,把错误另列:

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

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

收口错误

  • 提交后超时或状态不明时,不要再次点击发布;先去内容列表重载回读,防止重复内容。
  • 主提交按钮进入加载态、页面跳转或出现成功提示后,均视为已经尝试提交;后续只做列表回读。
  • 上传或处理失败时记录页面原文、文件名、预检摘要、账号、当前 URL、最后状态与发生时间; 保留源视频不变。
  • 页面显示转码失败(如「视频转码失败,调整视频导出参数后重试」)时,这是服务端拒绝, 不是网络问题:重传同一文件只会再失败一次。先核对文件大小与时长确实在页面显示的限制内, 再按 重压方案 消除预检偏离项、重压并重新验证。
  • 重发同一素材时列表里会同时存在旧条目与新条目,且文件名、时长、首帧完全相同。此时禁止 用这些字段做指纹,改用提交时间、标题、话题组合、新封面或条目 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 的分工与不做什么,见 组合决策。

我的收藏