本文是「Agent 开发实践与思考」系列第 8 篇。系列目录:
- 2024
- 2025
- 01-28 长任务不失忆:把 Vue 转 React 做成可验证的迁移流程(本篇)
窗口没满,任务已经失控
这里讨论一个超过两万行的 Vue 页面迁移到 React 的场景。查询条件、表格列配置、权限分支、弹窗、批量操作、路由参数、接口编排和埋点,可能都在同一个文件或紧密耦合的目录中。迁移后代码可以拆成多个组件和 hooks,但用户看到的行为不能变化。
直接把旧代码、目标框架和迁移要求交给 Agent,要求它持续修改直到完成,前十几轮往往没有明显问题。读过几批代码、输出多次搜索结果和构建日志后,模型可能遗漏已经确认的权限分支,重新提出已经否决的拆分方案,或在局部模块通过类型检查后把整个页面标记为完成。
例如,旧页面规定:批量审批成功后,列表必须使用当前筛选条件重新请求,并清空已选行。Agent 在审批弹窗中能完成接口调用,却可能在后续拆分列表模块时改成无参数刷新,或者忘记重置选中状态。旧代码和原始要求仍在对话历史里,但这条跨模块规则没有进入当前修改的依据。
这类问题和上下文长度有关,但不只是窗口不够大。大型迁移会产生文件全文、跨目录搜索结果、重复构建日志、失败尝试和接口响应等过程材料。任务目标、已确认行为、当前进度和验证证据如果只依赖对话顺序保存,历史越长,有效信息越难被稳定地带入下一次决策。
长任务需要把对话和任务状态分开。对话用于分析、搜索和协作。任务状态用于保存当前事实、证据、风险和完成条件。模型不需要逐字保留所有历史,但每次继续工作前都需要重新获得当前决策所需的约束。
完整的迁移过程可以画成一个受控闭环:
图中,源端事实先被提取为行为规格、验收基线和任务图;执行循环每轮都读取 checkpoint,把有限范围交给子任务;测试结果再更新 checkpoint。未通过的结论携带证据回到当前工作包,已验证的结论进入下一工作包。这样做有三个直接效果:
- 决策材料来自结构化状态,不依赖对话顺序。
- 压缩发生在工作包完成后,不中断正在进行的推理。
- 子任务只返回结论与出处,跨包判断仍由持有全局状态的主任务完成。
迁移的对象是行为,不是 Vue 文件
Vue 页面里的业务规则不只在模板中。接口定义、watch、computed、生命周期、store、路由守卫、权限函数和弹窗回调,都可能参与同一条用户链路。开始写 React 前,需要先确认迁移对象:哪些行为必须保持,哪些历史实现可以替换。
data、ref 和 reactive 要明确状态归属。computed 要确认它是渲染期派生值,还是需要缓存的计算。watch 和 watchEffect 要拆出触发条件、清理逻辑和请求竞态处理。provide/inject、v-model、slot、指令和生命周期也需要分别确定目标端实现。
watch 往往最容易被低估。一个 watcher 可能同时处理筛选条件变化、页码重置、请求列表、路由同步和取消过期请求。如果只翻译模板和接口调用,页面也许能打开,但筛选、浏览器前进后退、并发请求和权限切换可能与旧页面不一致。
因此,源端代码需要先整理为行为规格。规格记录用户动作触发的状态变化、请求和页面反馈,并保留能够回到源码或运行时核验的出处。React 的实现可以变化,行为规格仍是验收依据。
先生成迁移所需的资料
迁移前可以从 Vue 源码中整理四类资料:
- 接口资料。记录接口路径、参数来源、响应字段、调用场景、错误处理和成功回调。
- 状态与逻辑资料。记录页面入口条件、状态、派生关系、事件处理、副作用、请求触发条件和异常路径。
- 视图资料。记录组件层级、显示条件、表单绑定、列表列配置、插槽内容、国际化文案和权限入口。
- 测试资料。记录正常流程、参数非法、本地校验、接口失败和跨步骤串联场景,以及所需 Mock 数据。
以批量审批为例,接口资料应说明请求携带哪些选中项,成功后是否需要重新查询。状态资料应说明 selectedRows 被哪些操作读取,筛选条件和页码来自哪里。视图资料应说明操作按钮、表格列和弹窗入口各自受哪些权限控制。测试资料则应覆盖提交成功、提交失败、切换筛选条件、列表刷新和按钮状态变化。
这些资料不要求一次生成得很完整,但每条结论都必须带出处。模型可以把一段源码解释为行为说明,却不能把它自动视为事实。迁移清单需要记录结论的确认状态:
行为: 批量审批成功后的列表恢复
状态: observed
源端证据:
- src/views/OrderWorkbench.vue:1180-1237
前置:
- 已选择至少一行
动作:
- 提交当前 selectedRows 对应的订单
预期:
- 使用当前 filters、page、pageSize 重新请求列表
- 清空 selectedRows
- 更新批量操作按钮状态
React 落点:
- src/features/order-workbench/hooks/useBatchApprove.ts
验证:
- Playwright: batch-approve-preserves-queryobserved 表示已从源码或运行时观察到。模型根据上下文推断、但尚未确认的内容应标记为 inferred。业务或原维护者确认后可标记为 approved。已有测试覆盖的条目可标记为 covered,已经写入 React 但未回归的条目是 migrated,目标端验证通过后才是 verified。
这套状态的作用是区分“说明已生成”“代码已写入”“行为已验证”。迁移过程中的多数误判,都发生在这些状态被混成同一个结论时。
这里有一个更通用的原则:模型输出的自然语言不能作为任务事实。无论它生成的是说明、计划还是代码 diff,都要先进入某个可复核的对象,比如规格条目、源码、测试、issue、审批记录或日志。迁移状态只引用这些对象及其版本,模型在下一次循环中重新读取证据。
工作包是任务图中的节点
超过两万行的页面不适合作为一个连续任务交给一个 Agent。按代码行数或 Vue 模板区段切分,也容易把同一条业务链路拆散。更合适的切分单位是工作包:它有输入、边界、产物、依赖和验收项。
一个页面通常可以拆成以下工作包:
- 页面进入、路由参数和初始状态。
- 查询条件、分页和列表请求。
- 列配置、权限和可见性。
- 行操作和详情抽屉。
- 批量操作。
- 新增或编辑表单及其校验。
- 写操作成功后的刷新链路。
- 埋点、错误提示和异常状态。
每个工作包要声明输入状态、写入状态、依赖接口、受影响模块、目标代码位置和验收项。写操作后的刷新链路通常应单独列出,因为它连接了表单、接口、筛选、分页、列表和选择状态,容易在局部改动中遗漏。
工作包之间还要显式记录依赖。批量审批依赖列表选中状态,刷新依赖筛选参数和分页,权限会同时影响操作按钮、表格列和详情入口。这样,计划不再只是“组件 A、组件 B、组件 C 已完成”的待办列表,而是一张包含行为依赖和验收条件的任务图。
Agent 可以把一个工作包分给子任务,但主任务必须保存工作包之间的边和全局验收状态。子任务适合做全仓库检索、接口字段比对、旧页面行为盘点或单个组件实现。是否可以删除 Vue 适配层、是否可以切换流量,需要同时查看工作包依赖、调用方状态和回归结果,应由持有全局状态的任务判断。
这意味着主任务和子任务不是同一种 Agent。子任务接收明确问题、禁止修改超出范围的对象、返回带出处的结论;主任务负责组装上下文、维护任务图、执行验收和决定是否进入下一工作包。把两者混在一个长对话里,子任务的局部完成信号就会污染全局判断。
用旧页面建立测试基线
迁移开始前,应先为旧页面补充特征测试。特征测试记录旧实现当前表现,不先判断它是否合理。React 页面使用同一份测试和 Mock 验收,才能讨论行为是否一致。
测试可以按风险分层。参数转换、权限判断和纯状态机适合单元测试。表单校验、弹窗确认和按钮可见性适合组件测试。接口参数、成功和失败后的状态变化适合 Mock 集成测试。路由同步、批量操作、浏览器前进后退等关键链路适合端到端测试。复杂布局和视觉细节可以保留固定的人工验收步骤与截图证据。
优先冻结的行为通常包括:
- URL query 如何初始化筛选条件、页码和列表。
- 修改筛选条件后是否重置页码。
- 浏览器前进和后退后页面状态是否恢复。
- 多次请求并发时,哪个响应可以更新列表。
- 不同权限下按钮、表格列、菜单和详情入口的可见性。
- 写操作成功后,刷新请求是否保留当前筛选、分页和选择状态。
- 写操作失败后,表单输入和提示信息如何保留。
- 埋点事件名及关键字段。
类型检查通过只说明一个范围内的静态约束满足。它不能证明接口参数、权限分支或用户操作与 Vue 基线一致。每个 verified 状态都应关联测试命令、人工验收路径或截图等证据。
checkpoint 是任务的控制面
对话摘要和任务状态解决不同问题。滚动摘要压缩已经结束的讨论,并保留最近几轮原文来处理“刚才那个弹窗”“沿用上面的字段映射”这类指代。它适合记录已确认、待确认、已否决和下一步,不适合承担完整任务状态。
checkpoint 是每轮开始都要读取的稳定状态,只在任务事实变化时更新。它记录当前工作包、已验证行为、关键决策、风险和验证证据。对这个迁移任务,checkpoint 可以写成:
目标:
- 将 OrderWorkbench 从 Vue 迁移到 React,保持关键用户路径一致
当前工作包: 批量审批与刷新链路
已验证:
- 筛选条件与路由参数双向同步
- 列表分页请求参数与 Vue 基线一致
进行中:
- 审批成功后的刷新、选中状态重置与错误提示
关键决策:
- 按用户行为和状态边界拆分,不按 Vue 文件区段拆分
- 刷新函数接收当前 query,不由各模块自行拼接请求参数
风险:
- 权限同时影响操作按钮、表格列和详情入口,尚未完成全路径核验
验证证据:
- command: pnpm typecheck
result: passed
scope: src/features/order-workbench/list将 checkpoint 放在对话之外,意味着 Agent 的推理可以中断、会话可以切换、子任务可以失败,但任务状态不会随着聊天历史被压缩或改写。代码、测试结果或工作区版本与 checkpoint 冲突时,应重新读取代码和验证结果。checkpoint 记录的是当前事实,不是对旧讨论的概述。
会话结束时,交接的也应是这份可恢复状态。只写“继续迁移订单页”没有操作价值。下一个 Agent 需要知道当前工作包、已验证结论、待确认问题、源端证据、React 落点、未提交改动和下一步验证动作。
上下文只保留当前决策需要的信息
压缩一定会损失信息。是否可以压缩,不取决于内容来自代码、工具还是人工,而取决于下一步是否仍需逐字引用。
搜索到的全部命中文件、已经读取过的旧组件全文和单次构建的完整输出,大多可以在提取结论后丢弃。例如列表模块检索完成后,主上下文保留“selectedRows 被批量审批、导出和撤销使用”及其文件和行号;原始搜索结果保存在可重新读取的位置。
仍在修改的 React 模块、旧页面中尚未解释的条件分支、接口字段契约、待核验的异常日志和人工确认的迁移规则不能只留在摘要中。它们是当前工作的证据,需要以路径、版本和读取条件被重新访问。
工具输出刚返回时就应完成这次筛选。对于可复核的探索结果,保留结论、出处和下一步;原始材料落在仓库、任务存储或可访问的日志中。对于未解释的错误、高风险操作结果和需要逐字段比较的数据,保留原始内容或精确出处。
这样做的目的是降低无关历史对当前决策的干扰,上下文长度本身不是唯一目标。模型在处理“批量审批成功后如何刷新”时,需要的是刷新规则、涉及状态、接口契约和已有测试,而不是此前几十轮搜索日志。
上下文应被视为任务运行时重新组装的输入,不能依赖模型自带的对话记忆。长任务的可靠性来自组装过程是否稳定:哪些证据必须重新读取,哪些结论可以带入,哪些过程材料只保留路径。这些规则需要显式设计,不能交给滚动摘要自行决定。
在工作包边界压缩与重组
上篇讨论过 prompt caching:前缀完全一致时才能命中,修改中间历史会让之后的缓存重建。频繁重写滚动摘要,同时影响缓存命中和上下文稳定性。
工作包完成是更合适的压缩时机。比如“列表与列配置”验收完成后,准备进入“批量审批”时,前一个工作包的大部分原始过程已不需要逐字带入新任务。此时更新 checkpoint,保存源端和目标端证据位置,将结论写入摘要,并清理可以重新检索的原始工具输出。
新的消息前缀可以按稳定性排列:系统提示词、工具定义、页面迁移总约束、阶段内冻结的 checkpoint 和已完成工作包摘要放在前面;当前工作包的检索结果、增量对话和临时执行状态放在后面。阶段内部尽量通过尾部追加推进,避免反复改写历史中部。
工作包比固定轮数更适合作为压缩触发条件。十轮对话可能只解决一个权限分支,也可能已经完成一条独立业务链路。只有工作包完成且验收状态变化时,压缩才对应一个可复核的事实变化。
将规则交给测试和工具执行
“所有写操作先在测试环境验证”“审批后刷新并清空选择”“无权限时入口不可见”不能只存在于提示词或交接文档中。提示词可以解释意图,但不能承担正确性和安全性校验。
环境约束应由工具参数和凭据隔离。测试与生产环境使用不同配置,生产写操作由工具侧策略拒绝、要求人工确认或进入独立审批。页面行为应转换为断言,在相关工作包结束后执行。
对于批量审批,断言至少包括:请求携带选中项;成功后使用当前 query 刷新;选中项被清空;失败时保留必要上下文并显示预期错误。视觉细节和复杂交互难以一次性自动化时,应保留固定的人工验收步骤和截图证据。
模型适合做源码检索、规则提炼、代码初稿、测试骨架和差异检查。旧逻辑是否属于必须保留的业务规则,生产写操作是否允许执行,Vue 页面是否可以下线或切流,仍需要业务和工程责任人依据测试证据决定。
迁移流程
Vue 转 React 的长任务可以按以下顺序推进:
- 从 Vue 页面及其依赖中提取带源码出处的行为规格。
- 为关键路径补齐旧端特征测试、Mock 和人工验收基线。
- 按用户链路拆分工作包,记录状态读写和依赖关系。
- 在行为规格和测试约束下生成 React 代码。
- 每个工作包完成后更新 checkpoint,只将有证据的结果标为
verified。 - 完成跨工作包的链路回归后,再决定灰度切换和旧页面的回退安排。
长任务 Agent 的设计提炼
把这个案例抽象出来,长任务 Agent 至少需要六类状态与边界:
- 事实状态:已观察、已推断、已确认、已覆盖、已迁移、已验证分别记录,不能用“完成”混称。
- 任务图:工作包有输入、边界、产物、依赖和验收项,主任务保存跨包关系。
- 证据索引:每条结论能回到源码、测试、日志、截图或人工记录,checkpoint 只保存路径和版本。
- 执行边界:生产写操作、权限变更、删除旧代码和切流由工具策略或人工审批控制。
- 上下文组装规则:稳定约束放前面,当前工作包材料放后面,在工作包边界压缩,避免频繁改写历史中部。
- 恢复协议:会话中断后重新加载目标、当前工作包、证据、风险和下一步;代码和 checkpoint 冲突时重新验证。
这个流程适用于代码迁移,也适用于跨仓库改造、复杂排障和需要多轮验证的自动化任务。任务规模扩大后,模型仍会面对相同的问题:哪些事实已经证明,哪些状态尚未验证,哪些局部改动会影响其他链路。可靠性来自可恢复的状态、可追溯的证据和可执行的验证,而不是把完整历史一直留在上下文中。

