如何完成一次 Agent 辅助的软件开发任务

结合 SyndiRelay、Remote Browser MCP、VidOtter 等项目,梳理 Agent 辅助开发从需求、方案、实现到验证与交付的八个环节,并提供通用 AGENTS.md。

中文发布于 约 16 分钟读完
如何完成一次 Agent 辅助的软件开发任务:八个开发环节

1. 引言

这段时间,我陆续使用 AI Agent 开发和重构了几个项目,包括 VidOtter、SyndiRelay、Wardrobe,以及 Remote Desktop MCP、Remote Browser MCP 和 DevEnvironment。项目涉及浏览器扩展、本地服务、MCP、Docker 开发环境和跨平台安装。做的项目多了,也发现有些问题总会重复出现:需求散落在对话里,Agent 没理解现有系统就开始修改;几个任务共用环境时互相干扰;代码测试通过了,实际安装和使用却仍然有问题。

为了让 Agent 能参与真实开发,我还给它接入了文件、终端和浏览器操作能力。但工具能用,并不等于一次开发任务就能顺利完成。需求怎么管理、问题怎么定位、代码怎么隔离和验证、什么时候才算真正交付,这些仍然需要明确的方法。

所以我想把这段时间的开发经历整理出来,复盘哪些做法有效、哪些地方踩过坑,归纳一套简单、可以复用的 Agent 辅助开发流程,再把常用的工程约定放进 AGENTS.md。

2. 如何完成一次软件开发任务

下面以一次软件开发任务的生命周期为主线,从需求、分析、设计、开发、测试、合并、交付、复盘八个环节,结合实际项目中的问题,梳理相应的解决方法和工程经验。

2.1. 记录 Issue,明确需求与验收

SyndiRelay 用来将博客文章分发到不同内容平台。它提供命令行(CLI)、MCP 和浏览器扩展(Extension)等入口;涉及平台登录状态的操作由 Extension 处理。

有一次,使用 CLI/MCP 创建头条草稿时,任务一直等不到浏览器端处理,最后超时;但打开 Extension,点击同一平台的“创建草稿”,却能够成功。文章本身能够正常编译,问题显然不只是“发布失败”这么简单。

更麻烦的是,创建草稿属于远端写入。**如果请求超时,不能一概认为对方没有收到请求。**直接重试有可能创建两篇一样的草稿。于是这次任务至少要回答三个问题:为什么两个入口行为不同?怎样自动唤醒浏览器处理器?超时后如何确认远端状态?

排查需要检查 Extension 的 MV3 后台逻辑、修改唤醒机制、运行 Mock 测试,最后再核对真实平台的草稿状态。如果这些事项只留在聊天记录里,很容易把代码完成误认为整个故障已解决。

这个问题记录在 SyndiRelay Issue #10 中,包含故障现象、预期行为和验收条件;后续通过 Pull Request(PR)提交代码修改。主要内容如下:

## Problem
通过 CLI/MCP 创建头条草稿时,任务等待浏览器处理器直至超时;
从 Extension UI 发起同类操作则能成功。

## Expected
CLI/MCP 发起任务后,自动唤醒已有浏览器处理器,
无需手动打开 Extension 任务页面。

## Acceptance
- [ ] 有效的 CLI/MCP 任务能及时唤醒处理器,不抢占浏览器焦点。
- [ ] 超时且远端写入结果不明时,先核对草稿列表,不盲目重试。
- [ ] 使用 Mock 传输覆盖任务状态和唤醒逻辑,不进行真实平台写入。
- [ ] 保持“仅创建草稿”,不自动发布文章。

## Scope
只修改任务唤醒与相关测试;不重写文章编译器,
不改变账号授权或浏览器会话的边界。

Problem 用于定位和复现,Expected 描述期望行为,Acceptance 定义验收条件,Scope 限定改动范围。复杂问题可以补充日志和调用链;一次性的小修改可以直接在 PR 中说明。

后来 PR #14 与 PR #21 分别改进了定时和即时唤醒。真实草稿操作仍需授权验收,因此 Issue #10 继续保持 Open。

Agent 可以协助整理故障和起草 Issue;创建远端草稿、合并 PR 和关闭任务,则按实际授权和验收情况处理。

小结: 先把实际故障和风险说清楚,再抽象出 Problem / Expected / Acceptance / Scope。Issue 记录目标与验收,PR 记录实现。

2.2. 理解现有系统

以 Remote Browser MCP 为例。它通过 Chrome DevTools Protocol(CDP)连接本机 Edge/Chrome,让 Agent 可以直接操作真实浏览器、检查页面状态。

一次连接排查中,Edge 已经授权远程调试,但连续调用 browser_status 仍反复弹出 Allow remote debugging?,有时还会报告浏览器不可用。

如果只看报错,容易得出“浏览器没启动”“权限失效”的结论,然后反复重启或增加重试。但当时已有的 CDP 连接实际上可以工作,说明需要分开检查 MCP 连接、探针和浏览器自身的状态。

沿着调用链追踪后,发现旧版健康检查每次都创建新的 CDP WebSocket。Edge 对新连接要求授权,探针等待期间又把超时解释成连接失败。真正的问题不是原来的会话失效,而是健康检查本身改变了被检查的状态。

修复前可以建立一个简单基线:在同一个 MCP 进程里连续调用健康检查,记录是否新建连接、是否弹授权框、是否发生误报。然后再动代码。最终 PR #6 改为复用健康连接、合并并发探测请求,连接断开时再重建,并记录了测试和 Edge 环境复测。

从这件事总结出来的检查顺序是:先确认当前代码与进程状态,尝试复现,画出实际调用链,再定位失败发生在哪一层。Agent 可以用终端读取 Git 状态、日志和配置,用源码搜索或 LSP 追踪调用;涉及浏览器时再使用授权的 Remote Browser MCP。工具连接失败或索引不对应当前分支时,改用源码搜索,并在结果中列明尚未核实的部分。

小结: 修改前尽量复现故障,留下输入、环境和实际结果;先定位失败层次,再决定修改哪段代码。

2.3. 分析问题与设计方案

方案设计主要会遇到两种情况:一种是已经出现故障,需要定位根因并比较修复方案;另一种是需求明确,但实现方式还不确定,需要先验证一条可用的路径。

2.3.1. SyndiRelay:从并发崩溃定位共享状态

前面提到的 SyndiRelay,除了向平台创建草稿,还需要把文章转换成不同平台的内容格式。在一次 Windows CLI 测试中,并行编译 11 个平台的内容时,Go 进程直接崩溃:

fatal error: concurrent map writes

这是 PR #13 处理的问题。检查 SyncService.Run 和 publishing.Normalize 后发现,每个 Goroutine 虽然都拿到按值传递的 Config,其中的 Platforms 却是 Go Map。复制结构体不会复制 Map 的底层数据,mergeDefaults 因而在多个任务中写入了同一个对象。

可以直接取消并发,也可以加锁,还可以在归一化前复制 Map:

方案 能否避免崩溃 留下的问题
顺序执行 可以 放弃并行,归一化仍会修改调用者的 Map
给共享 Map 加锁 可以 继续维护共享可变状态和锁
先复制 Map 再归一化 可以 有复制开销,需确认嵌套字段的引用边界

最终选择先复制 Map:

cloned := make(map[string]PlatformConfig, len(config.Platforms))
for name, platform := range config.Platforms {
    cloned[name] = platform
}
config.Platforms = cloned

判断依据是数据所有权:归一化函数不应该悄悄修改调用者持有的配置。PR 随后用 128 个 Goroutine 的测试、go test -race 和 Windows 原生 Dry Run 验证,并核对相关配置与任务文件没有被改写。

碰到这类故障,方案设计至少要说清楚根因、可选做法、最终取舍和验证方法。涉及共享状态或跨组件协议时,尤其需要先明确数据由谁持有、谁有权修改。

2.3.2. Wardrobe:先验证最小可用闭环

有些需求的目标很明确,但采用什么交互方式还需要验证。直接让 Agent 一口气写完 UI、后台、配置和安装脚本,可能投入很多工作后才发现核心交互不合适。

例如,Wardrobe 的浏览器侧边栏原先依赖本地服务。服务没启动时,连导航都打不开。

最先需要验证的是:打开侧边栏 → 显示离线界面 → 服务恢复后继续使用原数据。因此先复用现有页面实现离线展示,再考虑自动启动。PR #2 解决了离线界面问题,PR #3 才增加 Native Messaging 自动启动。

这里用到了快速原型的思路:先实现最小范围的交互,确认方案可行,再决定继续完善哪些部分。原型(Prototype)主要验证技术或交互;MVP 则要求核心用户任务能够完成。原型可以在验证后丢弃,也可以继续演进;选择后者,就要逐步补齐测试、状态管理和异常处理。

这套方法不限于浏览器扩展。CLI 工具可以先跑通一个命令,MCP 工具可以先完成一次真实调用,后端服务可以先实现一条端到端请求链路。优先复用成熟组件,但如果复用成本高于实现一个最小方案,也没有必要勉强。

交互方案确定时,还应考虑用户从哪里进入、需要哪些配置、出现故障后如何恢复。安装与发布则留到交付阶段处理。

小结: 已知故障先分析根因并比较方案;新功能先验证最小闭环。两种情况都要明确改动范围和验证方法。

2.4. 隔离环境并实现修改

多个任务并行时,代码和运行环境都可能互相干扰。Worktree 处理工作目录冲突,Docker 则按需处理工具链与服务依赖。

2.4.1. Reverfolio:用 Worktree 隔离并行修改

Reverfolio 用于统计网站、文章和开源项目的传播情况。安装与 MCP 管理、影响力事件、采集失败恢复等功能曾分成不同分支开发。多个 Agent 如果共用一个工作目录,一个任务切换分支或更新依赖,就可能影响另一个任务尚未提交的修改。

Git Branch 区分提交历史,但一个工作目录同一时间只能检出一个分支。Git Worktree 可以为不同任务建立独立目录:

git status --short --branch
git worktree list
git fetch origin

# 确认仓库默认分支为 main 后再执行
git worktree add ../project-fix -b fix/example origin/main

Agent 创建新 Worktree 前,先检查已有目录与未提交修改。清理工作区时,确认没有未合并提交,也没有其他任务仍在使用。Worktree 只隔离源码目录,共用的 SQLite、端口、浏览器 Profile 和构建输出仍可能互相影响。

2.4.2. Docker:统一工具链与服务环境

代码目录分开后,还可能遇到环境不一致:不同 Go、Node 或 Android SDK 版本导致构建结果不同,多个服务占用相同端口也会影响测试。

DevEnvironment 用 Docker 镜像固定 Linux 开发工具链,减少在不同机器之间反复配置 Go、Node 等依赖的成本。

需要同时运行多个服务时,可以使用 Docker Compose。例如 Oncall Lab 的 RPC 故障排查环境,包含 Gateway、RPC 服务和监控组件,通过 Compose 组织和启动。它与 DevEnvironment 的用途不同:前者组织多服务实验,后者统一开发工具。

隔离对象 方法 仍需检查
不同任务的代码与未提交修改 Git Worktree 共用目录、分支及未提交文件
编译器和 SDK 版本 DevEnvironment / Docker 镜像版本、挂载路径、缓存
集成测试中的服务和数据 Docker Compose 端口、数据库、卷和测试数据
浏览器与原生安装 真实目标系统 容器不能替代 Windows/macOS 验收

文档修改或简单 UI 修复通常不需要 Docker。只有工具链版本、多个服务或运行状态可能影响结果时,才值得引入。容器也不要直接读写用户正式的 Data/ 或浏览器凭据。

环境隔离完成后,再按逻辑单元修改、执行相关测试、检查 git diff 和 Commit;获准后 Push,核对远端提交。隔离是为了减少干扰,不必因此增加额外的开发流程。

小结: 并行任务先分开代码工作区;遇到工具链或服务依赖问题时再用 Docker。修改仍按小步验证和提交推进。

2.5. 验证修改

以 VidOtter 为例。它通过浏览器发现视频资源,再交给本地程序下载。一次升级测试中,用户在浏览器扩展的弹窗里启用可选的 ChatGPT Tunnel Client,却看到“不支持的 Tool 操作”。

检查发现,磁盘上的 Popup 和 Bridge 已是新版本,但 Edge 仍运行旧的 Manifest V3 Service Worker。新界面发出的命令,旧 Worker 并不认识。这个故障靠 Go 单测或安装包文件检查无法发现,需要验证浏览器里真正运行的组件。

要验证哪一层,就选对应的检查方法:

需要证明什么 怎样验证
逻辑和并发安全 单元测试、竞态测试
组件之间协议一致 API/消息处理器的集成测试
用户真的能操作 真实 Web UI、Extension 或 CLI 的关键路径
安装和升级可用 目标系统的安装、运行时版本、数据保留检查

Bugfix 先对照修改前基线确认原问题消失,再覆盖受影响的正常路径;新功能则依据 Issue 验收条件。记录实际运行的命令和结果,明确 Passed / Failed / Not Tested。交叉编译、Docker 内通过和真实 Windows/macOS 安装是不同的证据,不能混写。

小结: 验证对象要对应真实故障层次;Agent 提供可复查的测试证据,未测内容直接列明。

2.6. 提交与合并

以 博客站点 为例。它通过 Inkerio 渲染文章和项目页面,项目地址在站点仓库中配置。DownKit 更名为 VidOtter 后,旧文章中的 /projects/downkit/ 链接失效。这个问题出在站点路由,不需要修改渲染引擎。

因此 站点 PR #271 只修改站点配置、重定向生成脚本和测试,让旧地址跳到新的项目路径,并检查重复映射、链式跳转和循环跳转。把这种修改直接推到主分支,后续就难以明确是谁调整了路由、验证了什么。

PR 集中呈现代码改动、修改原因、测试结果和关联 Issue,供合并前审查。Agent 可以准备 Diff、提交 PR、查看 CI;Merge 和 Release 仍按任务授权分别进行。

提交前可以运行 git status --short --branch、git diff --check、git diff --cached 检查当前工作区和暂存内容;Push 和合并后再核对远端分支状态。

这些项目采用 Squash Merge:开发分支可以按逻辑单元逐步提交,合并时压缩为主分支上的一个变更。要求 PR、禁止强推等限制需要配置 GitHub Rulesets/Branch Protection,单靠 AGENTS.md 不会自动生效。

另外,PR 解决了代码,不等于用户一定已经收到更新。需要等 Release 或现场验收的 Issue,不应只因某个 PR 合并就用 Fixes #N 自动关闭;合并后还要核对远端状态,安全清理已经完成的分支和 Worktree。

小结: 让 PR 承担代码审查与合并,让 Issue 继续跟踪未完成的验收;保护主分支,按授权 Merge。

2.7. 产品化与交付

VidOtter 的下载功能已经可用,但第一次安装仍涉及启动本地 Go Bridge、注册用于扩展与本地进程通信的 Native Messaging、加载浏览器扩展,以及检查连接状态。对不熟悉内部组件的用户而言,这些步骤并不直观。

完整的用户路径是:下载 → 安装 → 首次配置 → 启动 → 完成核心操作 → 升级 / 卸载。

2.7.1. 安装方式怎么选?

像 VidOtter 这样包含 Bridge、Native Messaging 和浏览器扩展的工具,仅提供压缩包仍要用户手动完成多步配置。是否引入安装器,应当先比较实际使用成本:

方式 适用情况 维护成本
压缩包 CLI、便携工具 用户手动配置较多
安装脚本 少量需要注册的本地组件 处理不同平台脚本和失败恢复
图形安装器(如 Qt IFW) 多组件、频繁安装或升级的软件 安装器逻辑与多平台测试

选择标准是用户需要多少手工步骤,以及项目有没有能力维护安装与升级流程。

2.7.2. 第一次运行应该怎样引导?

安装脚本可以复制文件、注册 Native Messaging,却无法替用户完成所有浏览器授权。未上架的扩展仍要由用户确认加载;像 ChatGPT Tunnel 这样的可选集成,也不应该成为普通下载功能的前置条件。

由此得到一个可以复用的检查方法:找一个没有参与开发的人,让他不看源码,从下载开始完成一次核心操作。 哪一步需要开发者解释内部术语,就说明首次使用流程还不够清楚。

2.7.3. 升级失败了怎么办?

升级不能只检查程序文件是否替换成功,还要确认后台进程、浏览器 Worker 与本地通信协议已经使用预期版本。

更重要的是用户数据。Wardrobe 使用 SQLite,VidOtter 使用 Data/jobs.json 和 TOML 保存任务与配置;升级和卸载都需要保护这些数据及下载文件。如果涉及数据库 Schema 迁移,直接换回旧二进制也未必能够恢复。

因此针对涉及数据迁移的升级,先准备匹配版本的备份,完成后检查程序健康状态;在隔离数据中验证必要的恢复路径。不能把“安装器能卸载”当成“产品已经具备安全回滚”。

2.7.4. 不同平台怎样算验收?

这种交付问题不只出现在 VidOtter。之前给 SyndiRelay 制作跨平台发布归档时,PR #4 发现 Windows 上生成的 ZIP 没有保留 Linux/macOS 程序应有的执行权限。编译产物没有问题,用户解压后却可能无法直接运行,最后需要修正归档权限位。

网站也一样:VidOtter PR #22 修复了博客展示时的图片链接问题;站点 PR #271 则补齐旧项目地址跳转。这些都不是靠一次本地编译就能验证的。

发布前应按目标平台检查资产、权限、首次安装和核心操作;README 要能让用户连续完成操作。只做了交叉编译的平台写清楚未实测,已经发布的旧归档也不会因为后来修改 README 就自动更新。

小结: 先让用户完成一次真实操作,再检查升级、数据保留与目标平台;安装包只是交付的一个环节。

2.8. 总结并沉淀

同样的问题在不同项目中反复出现时,就值得总结。比如 Remote Browser 的健康检查、SyndiRelay 的共享 Map、VidOtter 的旧 Worker,表面上分别是连接、并发和安装问题,但它们都有一个共同点:不能只看某个函数返回成功,要检查真实状态和系统边界。

PR 结束后,可以让 Agent 汇总故障现象、代码修改、测试结果和未完成项,再筛选可用于其他项目的做法。工具参数、平台协议和具体函数名留在项目约定中;“先复现、确认所有权、按真实用户路径验收”等行为规则再整理到通用 AGENTS.md。

比如第 2.1 节的例子,就可以提炼成一条很具体的规则:“涉及远端写入的任务超时,不得未经核对自动重试。”如果它只适用于内容分发工具,应放进 SyndiRelay 自己的约定,而不是强行规定所有项目都按同样协议工作。

小结: 从真实故障和验证结果中提炼方法,只把跨项目适用、可以执行的约定放进通用模板。

3. 以 VidOtter 为例

下面以 VidOtter 为例,梳理一次从需求到交付的完整开发流程。

3.1. 明确需求与完成标准

VidOtter 最初要解决的是一个直接的使用场景:浏览器可以正常播放媒体,但要保存允许下载的 MP4/HLS 资源,仍需手动查找媒体地址,再交给本地程序处理。目标是让浏览器发现资源并提交下载任务,减少手工复制地址和执行命令的步骤。

因此先不谈字幕、AI、安装器,普通媒体下载的第一条使用路径应该是:

浏览器打开并播放媒体
        ↓
Extension 发现 MP4 / HLS 请求
        ↓
创建本地下载任务
        ↓
Bridge 调用下载引擎
        ↓
任务完成,用户打开文件

下载闭环跑通后,又陆续遇到临时签名 URL 过期、ChatGPT 提交网页时尚未产生媒体请求、升级后新 Popup 与旧 Worker 不兼容等问题。浏览器自动化、MCP 和 Qt 安装器由此分别进入后续迭代。

以临时 URL 恢复为例,可以把验收条件收窄为:原任务等待新的浏览器媒体请求,拿到有效请求后继续原任务;不能直接重放已经失效的 URL,也不能把浏览器凭据写进持久化任务。 对外发布时,还需要额外验收安装、首次运行、升级及数据保留。

3.2. 理解现有系统

当下载任务遇到临时地址失效时,第一反应很容易是给下载器加重试。但浏览器里的请求、Bridge 的任务状态和下载器手中的地址不一定属于同一层。为了知道重试应该在哪里发生,先沿实际代码追了一遍调用链:

Edge / Chrome 网页
       │ 媒体网络请求
       ▼
extension/core/media.js + background.js
       │ 浏览器会话、临时媒体信息
       ▼
Native Messaging / 本地 Bridge 接口
       │
       ▼
bridge/browser_media.go + browser_wait.go
       │ 发现媒体、认领等待中的任务
       ▼
bridge/bridge_jobs.go
       │ 任务状态和下载调度
       ▼
bridge/downloader.go
       │ MP4/HLS 下载
       ▼
Data/downloads/(下载结果)

这里需要先分清数据由谁持有。

浏览器扩展负责观察实际媒体请求与页面会话;Go Bridge 管理任务、状态和下载。MCP 是可选入口,它可以提交网页、查询任务或通过受限浏览器接口触发操作,但不应该从 MCP 返回值里获得 Cookie、Authorization 或原始签名媒体地址。

代码也体现了这条边界:bridge/browser_media.go 的媒体索引保存在内存中;bridge/browser_wait.go 可以先创建 waiting_for_browser 任务,等用户播放并产生新请求后,使用同一个任务 ID继续下载;bridge/bridge_job_store.go 的 persistentBridgeTask 会剔除需要浏览器会话才能使用的 URL、Cookie 和授权头,避免写入 Data/jobs.json。

这样,下载失败时才知道该查哪一层:是浏览器没有产生新请求、Bridge 没有认领任务,还是下载器已经开始处理分片。不同阶段不能使用同一种重试方式。

3.3. 分析方案与取舍

一些媒体 URL 包含有效期或依赖当前浏览器会话;即使已经发现媒体,稍后下载仍可能返回 403 或地址失效。

当时可以考虑三种处理方式:

方案 直接结果 问题
为每个网站写专用解析器 针对当前页面可能奏效 维护成本高,也无法形成通用能力
下载失败后立即重试旧 URL 实现简单 地址或凭据已失效时反复失败,还可能错误复用下载检查点
保留任务,等待浏览器产生新的有效请求 复用原下载引擎与任务 ID 必须区分失败发生在哪个阶段,并正确处理会话信息

PR #16 采用了第三种思路。bridge/media_expiry.go 只解析明确命名的到期参数,支持秒、毫秒和 RFC3339;未知格式不假定已经过期,也不引入站点专用规则。对可识别的到期时间,当前实现设置了 30 秒安全边界。

恢复条件还需要收紧。bridge/bridge_jobs.go 只有在 media_expired / media_access_denied 这类原因、仍处于 resolving 阶段且存在浏览器页面上下文时,才把任务切回 waiting_for_browser。如果已经进入分片下载,就不能随意重置或重放旧的 HLS 检查点。

// bridge/bridge_jobs.go(关键判断,节选)
if status == "failed" &&
    (reason == "media_expired" || reason == "media_access_denied") &&
    job.Phase == "resolving" && job.PageURL != "" && job.SourceTabID >= 0 {
    job.Status = "waiting_for_browser"
    // 等待浏览器重新播放并产生新的请求
}

用户重新播放后,扩展提交新的媒体请求,Bridge 再尝试恢复原任务。这样不用另写一套下载器,也避免把“重试”变成无限循环。

这一阶段还要考虑隐私:Data/jobs.json 不持久保存原始浏览器凭据和带签名的媒体 URL。但 HLS 检查点可能在本地 Data/work/ 中保留部分敏感 URL,因此整个 Data/ 仍应作为私有运行目录,不能当成普通构建产物上传。

3.4. 隔离环境并实现修改

方案确定后,除了修改失败状态,还要防止其他页面的新媒体请求误认领当前任务。例如 bridge/browser_wait.go 的 claimBrowserWait 会核对浏览器提交的页面与等待任务是否匹配:

// bridge/browser_wait.go(节选)
if job == nil || job.BrowserPageHash == "" ||
    job.BrowserPageHash != browserPageDigest(task.PageURL) {
    return "", false, errors.New("browser page does not match waiting task")
}

后续还会检查任务是否仍处于等待期,以及是否已经由其他请求认领。符合条件才用原任务 ID 接续,避免重复创建下载任务。相关代码分布在 Extension 的捕获逻辑、Bridge 的媒体索引、任务状态和落盘处理里;每一层都应有对应的测试。

VidOtter 提供 go -C tools/vidotterdev run . verify 作为统一验证入口。Agent 修改 Extension、Bridge 或下载逻辑后,运行相关测试并检查 Diff;并行开发时再按需隔离工作目录和运行资源。

3.5. 验证修改

验证不能只停在“编译成功”。VidOtter 至少有三层需要分别检查。

第一层:真实下载链路。 PR #15 记录了 Windows 上使用公开的 MDN CC0 测试视频 完成一次 MCP 端到端操作:

vidotter_browser_status
    → vidotter_browser_open(专用后台标签页)
    → vidotter_list_browser_media(发现 MP4)
    → vidotter_download_browser_media(创建任务)
    → vidotter_get_job(completed,100%,1,128,375 bytes)
    → vidotter_browser_close(关闭测试标签页)

这次端到端验收覆盖的是 Windows + MP4 + MCP。HLS、DASH 和不同浏览器站点还需要各自的测试。

第二层:失败恢复与数据安全。 PR #16 的 Go/Node 测试与 go test -race 通过,新增测试覆盖时间格式、过期捕获、403 分类、旧媒体重放、任务 ID 保留和敏感字段脱敏。这一轮侧重自动化回归,未进行新的浏览器现场下载测试。

第三层:运行中的组件版本。 安装测试候选包后,用户实际遇到“不支持的 Tool 操作”:磁盘上 Popup 和 Bridge 已经更新,Edge 仍运行旧的 Manifest V3 Worker。PR #18 增加 bridge.mcp.capabilities 能力检查、扩展重载提示和独立的预览版本号,并让测试执行真正注册的 Worker 消息处理器。

对应的 三平台 CI 和 安装器候选包验收 均已成功。用户浏览器内的授权和交互,需要在实际安装环境中进一步检查。

3.6. 提交 PR、合并与清理

浏览器中继需要控制凭据边界,地址恢复涉及任务状态,安装器则会影响已有程序和数据。这些变更分别通过 PR #15、#16、#18 审查合并;随后 PR #19 处理发行准备,v1.6.3 发布后再由 PR #21 整理从安装到下载完成的 Quick Start。

PR 的拆分有实际意义:媒体恢复错误可以回到 #16 的状态机和测试定位;旧 Worker 的版本问题则对应 #18 的协议和安装器修改,不需要把所有功能放到一个巨大 PR 中审查。

对于自己的 Agent 工作流,仍按第二章的方式检查 Diff、相关测试、CI 与授权,采用仓库约定的 Squash Merge;合并后核对远端状态再清理分支。

3.7. 产品化与交付

核心下载能力跑通之后,普通用户仍然会卡在安装 Bridge、注册 Native Messaging、加载扩展和第一次连接上。为此,VidOtter 在 v1.6.3 引入正式的 Qt Installer Framework(Qt IFW)离线图形安装器,并继续提供便携归档作为备选。

v1.6.3 Release 提供 Windows x64、Linux x64、macOS Intel 图形安装器;Linux ARM64 和 Apple Silicon 提供便携包。安装器部署 Bridge、注册 Native Messaging,并引导用户激活扩展。扩展仍需用户在 Edge/Chrome 中手动加载。

交付验收还包括升级和卸载。仓库的 installer-smoke.yml 在三种目标系统上执行无人值守安装/卸载和数据保留检查,Linux 还有虚拟图形界面的安装向导测试。Data/、下载文件和个人配置不应随程序替换而消失;浏览器扩展更新后,还要核对运行中的 Worker 是否真的切换版本。

v1.6.3 尚未提供公开在线自动更新源;Windows 安装包未签名,macOS 安装包未经过 Apple 公证。浏览器授权、个人 Tunnel 登录和第三方媒体环境的可用性需在用户实际设备上检查。

这次产品化经验可以归结到一个可操作的验收动作:让一位没参与开发的人从 Release 下载开始,安装、加载扩展、完成一次测试下载,最后检查升级后的任务与文件是否还在。

3.8. 总结并沉淀

几次迭代后,可以归纳出三条做法:

  1. 先跑通一条真实任务。 浏览器发现 → 本地任务 → 下载结果,能够执行并验收之后再扩展可选的 MCP 和产品功能。
  2. 按状态和所有权处理故障。 浏览器会话、等待中的任务、下载中的检查点由不同组件管理;失败恢复必须知道处于哪个阶段,不能盲目重试或持久化敏感凭据。
  3. 以运行中的软件和真实安装行为验收交付。 源码、安装目录、活动 Worker、浏览器授权和用户数据可能各自处于不同状态,不能用一个构建成功代替全部结果。

4. 通用 AGENTS.md

本文所述开发流程已整理为可复用的 Agent 工程规范,并会持续更新。最新版本参见 AGENTS.md(简体中文)。

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看