Files
FlowPilot/docs/md/步骤与Flow节点化重构开发方案.md
T
2026-05-15 17:41:35 +08:00

16 KiB
Raw Blame History

步骤与Flow节点化重构开发方案

1. 文档目标

本文不是给现有 step 体系再补一层适配,而是把整个执行系统升级成 flow + node 体系。目标是从根上解决“数字步骤硬编码”和“key 绑定混用”这两套逻辑并存的问题。

最终设计必须满足:

  1. 新增步骤时,只改当前 flow 的定义和节点执行器,不再牵动全局 switch。
  2. 新增完全不同的 flow 时,可以拥有完全不同的节点图、页面源、邮件规则、恢复策略和 UI 表达。
  3. 最终版本不保留 currentStep / stepStatuses 作为核心状态,不保留 STEP_* 作为核心协议,不再让数字步骤充当身份、顺序、状态、历史和消息路由的共同主键。
  4. 方案必须同时覆盖 background、content、sidepanel、日志、历史、自动运行、来源注册、邮件规则、手机号接码、Plus 支付这些强关联模块。

2. 重构前实现的真实形态

当前项目不是纯硬编码,而是“定义层部分动态化,控制层仍然硬编码”。

现状 主要问题
状态 background.js 里仍有 currentStepstepStatuses 数字步骤同时承担状态、顺序和恢复锚点
协议 EXECUTE_STEPSTEP_COMPLETESTEP_ERRORSKIP_STEP 消息协议仍以数字 step 为核心
定义 data/step-definitions.jsbackground/steps/registry.js 只有 OpenAI 真正注册,且执行仍按数字 id
UI sidepanel/sidepanel.js 按 step 渲染、发送、跳过 侧边栏本质上还是数字步骤面板
内容脚本 content/signup-page.jsmessage.step 分支 页面驱动逻辑被 step number 绑死
运行态 background/runtime-state.js 已有 currentNodeIdnodeStatuses 但仍通过 legacy step 视图回写,双模型并存
外围能力 shared/source-registry.jsshared/flow-capabilities.jsbackground/mail-rule-registry.js 已有抽象,但都还没成为唯一事实来源

重点文件里最能说明问题的几个点:

  • background.jsDEFAULT_STATEsetStepStatus()skipStep()runAutoSequenceFromStep()getStepRegistryForState() 仍把数字 step 当主流程。
  • background/message-router.jsSTEP_COMPLETE / STEP_ERROR / EXECUTE_STEP / SKIP_STEP 还在消息层面锁死 step。
  • background/runtime-state.js:已经有 node 相关字段,但还在做 step 兼容派生。
  • sidepanel/sidepanel.js:步骤列表、状态展示、手动执行、跳过、自动运行都还围着 step。
  • flows/openai/mail-rules.jsbackground/mail-rule-registry.js:已经说明“规则可以 flow 化”,但目前只有 OpenAI 在用。

3. 为什么现在的设计有问题

3.1 混用了三种身份

现在一个“步骤”同时在扮演:

  • 执行身份
  • 顺序编号
  • 状态键
  • 日志标签
  • 历史记录字段
  • 自动运行恢复锚点

这就是后面维护越来越难的根因。只要新增一个节点,往往要同时改定义、状态、消息、UI、日志、历史、恢复逻辑,甚至测试。

3.2 只改 key 不够

项目里已经有 key,但它并没有真正替代数字 step。

当前状态是:

  • id 负责顺序和执行
  • key 负责局部标识
  • currentStep 负责运行态
  • stepStatuses 负责状态

这不是“灵活”,这是“双主键混用”。如果只把 step 改名成 node,不重做控制面,问题不会消失。

3.3 不能表达完全不同的 flow

现在 getStepRegistryForState() 直接在 activeFlowId !== openai 时拒绝执行,这意味着:

  • 新 flow 不是“可扩展”,而是“被排除”
  • 每个 flow 都只能往 OpenAI 这套结构里塞
  • 只要一个 flow 的步骤顺序、页面、邮件、恢复策略不同,就会触发大量全局改动

3.4 线性 step 模型太弱

现有逻辑默认所有流程都是线性的 1, 2, 3, 4...。

但真实场景里,新的 flow 很可能是:

  • 有分支
  • 有可选节点
  • 有重试回路
  • 有条件跳转
  • 有完全不同的外部页面驱动

这种场景下,数字 step 不是天然模型,只是临时排布方式。

3.5 相关模块已经开始“各自发明一套”

runtime-statesource-registryflow-capabilitiesmail-rule-registrynavigation-utils 都已经在做分层,但它们没有统一到一个真正的 workflow 主模型里。

结果就是:局部已经像架构,整体仍像拼接。

4. 目标架构

flowchart TD
  FR["flowRegistry"]
  WF["workflowDefinition"]
  WE["workflowEngine"]
  RS["runtimeState"]
  SR["sourceRegistry"]
  DR["driverRegistry"]
  MR["mailRuleRegistry"]
  CR["capabilityRegistry"]
  SP["sidepanel"]
  CS["content drivers"]
  LOG["logs / history"]

  FR --> WF
  WF --> WE
  WE <--> RS
  WF --> SR
  WF --> DR
  WF --> MR
  WF --> CR
  WE --> SP
  WE --> CS
  WE --> LOG

4.1 核心标识

标识 含义 规则
flowId 业务 flow 身份 例如 openaisite-a
runId 一次执行实例 同一轮运行内保持不变
nodeId 节点身份 唯一、稳定、可读
sourceId 页面来源身份 负责标签页、页面家族和注入目标
driverId 内容驱动身份 负责某个 source 的命令集

4.2 目标状态模型

runtimeState = {
  flowId: 'openai',
  runId: 'run_20260515_xxx',
  workflowVersion: 1,
  currentNodeId: 'submit-signup-email',
  nodeStatuses: {
    'open-chatgpt': 'completed',
    'submit-signup-email': 'running',
  },
  nodeResults: {
    'open-chatgpt': { completedAt: 1710000000000 },
  },
  shared: {},
  services: {},
  flows: {
    openai: {},
  },
  runSummary: {
    finalStatus: 'running',
    failedNodeId: '',
    failureReasonCode: '',
  },
}

4.3 节点模型

node = {
  id: 'submit-signup-email',
  title: '注册并输入邮箱',
  type: 'task',
  order: 20, // 仅用于展示,不参与身份
  sourceId: 'openai-auth',
  driverId: 'content/signup-page',
  next: ['fill-password'],
  retryPolicy: { maxAttempts: 3 },
  recoveryPolicy: { onFailure: 'restart-node' },
  ui: { section: 'registration' },
}

要点:

  • id 是唯一主键。
  • order 只是展示顺序,不再决定执行逻辑。
  • next / recoveryPolicy 决定流转,不靠全局 step 数组推断。
  • 节点结果、失败原因、重试记录要单独存,不要塞进状态字符串后缀里。

4.4 Flow 定义必须是一手真相

flowDefinition 应该统一拥有:

  • 节点图
  • 节点执行器引用
  • source 绑定
  • driver 绑定
  • 邮件规则引用
  • capability 定义
  • recovery policy
  • settings schema

其他 registry 如果存在,只能是这个定义的运行索引或编译产物,不能自己再维护一份同义信息。

5. 新增步骤 / 新增 flow 的接入规则

5.1 如果是在同一个 flow 里新增步骤

应该只做这些事:

  1. 在该 flow 的 workflowDefinition 里新增 node。
  2. 给这个 node 配置 nextretryPolicyrecoveryPolicysourceIddriverId
  3. 如果它访问了新页面、新邮件规则、新服务,再补对应的 source / driver / mail rule。
  4. UI 从 flow 定义里自动拿到节点标题、顺序和状态展示。
  5. 增加对应测试,不再改全局 step switch。

5.2 如果是新加一个完全不同的 flow

应该直接新增一个独立 flow 目录和定义,不去改 OpenAI 的步骤树:

flows/<flowId>/
  workflow.js
  sources.js
  mail-rules.js
  capabilities.js
  recovery.js

新 flow 的要求是:

  • 可以没有手机号接码
  • 可以没有 Plus
  • 可以没有邮件验证码
  • 可以有完全不同的节点顺序和分支
  • 可以有自己的页面源和 driver

核心 engine 不改,改的是 flow 自己。

6. 相关模块边界

6.1 background

background.jsbackground/message-router.js 的职责应退回成“调度和校验”,不再自己写业务步骤树。

6.2 sidepanel

sidepanel/sidepanel.js 只能根据 flow definition 和 capability 结果渲染,不应该再手写 step 编号和 step 顺序判断。

6.3 content scripts

content/signup-page.js 这类脚本要从“按 step 分支”改成“按 node action / command 分支”。

6.4 source / driver

shared/source-registry.js 只负责页面来源、标签页生命周期和注入范围。

driverRegistry 只负责“这个 source 能接什么命令”。

6.5 mail rules

邮件过滤和验证码提取必须是 flow-local 的。flows/openai/mail-rules.js 说明这件事已经存在,只是还没推广成整体原则。

6.6 auto-run / history / logs

自动运行、日志和历史必须记录 flowIdrunIdnodeId,而不是继续写 step7_failed 这种混合字符串。

6.7 手机接码、Plus、OAuth

这些都不是“通用步骤”,它们是 OpenAI flow 的私有能力。没有第二个 flow 的真实需求时,不要把它们硬抽成全局共享步骤。

7. 设计符合性检查

检查项 是否满足 说明
不做旧兼容 最终状态不保留 step 作为核心模型
新增步骤可维护 只改当前 flow 的 node 和相关依赖
新增不同 flow 可维护 独立 flow 定义,不污染 OpenAI
规范一致 统一使用 flowId / runId / nodeId / sourceId
完整性 覆盖状态、协议、UI、日志、历史、自动运行、来源、邮件
正确性 基本满足 仍需靠分阶段测试验证边界和恢复路径

8. 方案自身的缺陷与控制点

这份方案本身也有潜在问题,必须提前说明:

  1. 抽象过头风险 如果 flow / node / source / driver / mail rule 各自独立维护同一份信息,会重新制造同步成本。控制方式是:flowDefinition 必须是一手真相,其它 registry 只能是派生索引。

  2. DSL 复杂度风险 如果节点模型一开始就塞太多字段,后续会变成另一种难维护的配置语言。控制方式是:先保留 idtitletypeordersourceIddriverIdnextretryPolicyrecoveryPolicy,其余按真实需求再加。

  3. 日志和历史膨胀风险 如果把每个节点的所有中间态都原样落盘,历史会很重。控制方式是:日志保留可读摘要,历史保留最终结果 + 关键节点轨迹 + 失败原因码。

  4. UI 复杂度风险 如果 UI 同时展示 flow、node、source、driver、恢复策略,侧边栏会太吵。控制方式是:UI 只展示用户关心的流转和状态,详细调试信息留给开发记录。

  5. 边界冲突风险 现在已经存在 sourceRegistryflowCapabilitiesmailRuleRegistryruntimeState 等分层。如果不统一为一套 flow contract,就会继续出现“看起来都对,合起来互相打架”的问题。

9. 开发清单

阶段 开发目标 阶段自检
1 定义最终 schemaflowIdrunIdnodeIdsourceIddriverIdworkflowVersion 检查是否还在把 currentStep / stepStatuses 当核心概念使用
2 建立 flow definition 和 workflow engine 检查一个 flow 是否能只靠自己的定义被加载,不改全局 switch
3 替换消息协议 检查是否全面切换到 EXECUTE_NODE / NODE_COMPLETE / NODE_ERROR / SKIP_NODE 这类命名
4 改写 runtime / auto-run / recovery 检查恢复、重试、跳过、暂停是否都基于 nodeIdrunId
5 重写 sidepanel 检查 UI 是否完全从 flow definition 取数据,不再硬编码 step 顺序
6 迁移 content drivers、邮件规则、来源注册 检查新 flow 是否可以拥有完全不同的页面源和邮件规则,而不碰 OpenAI 核心流程
7 重写日志、历史、账号记录 检查是否只记录 flowId / runId / nodeId,不再产出 stepX_failed 这类混合状态
8 删除旧 step 路径和残留逻辑 检查仓库中核心路径是否还残留 EXECUTE_STEPSTEP_COMPLETESTEP_ERRORcurrentStepstepStatuses

每阶段统一自检

每做完一个阶段,都要执行下面的自检:

  1. grep 核心代码,确认没有新的 step 硬编码回流。
  2. 用至少一个 flow 做完整跑通验证。
  3. 用一个“结构完全不同”的 flow 做边界验证。
  4. 检查消息、日志、历史、UI、文档是否一致。
  5. 打开 Markdown 预览,确认中文标题、表格和代码块都没有乱码。

10. 最终验收标准

这次重构只有在满足下面条件时才算完成:

  • 核心状态不再依赖数字 step。
  • 新步骤只改本 flow,不改全局控制面。
  • 新 flow 可以和 OpenAI 完全不同。
  • background、sidepanel、content、auto-run、logs、history、mail rules、source registry 都在同一套 flow/node 规则下运行。
  • 文档、代码、测试三者描述一致,没有“代码一套、文档一套、UI 一套”的分裂。
  • Markdown 和中文显示无乱码。

结论很直接:这块不能继续修补式演进,必须用 node-based workflow engine 一次性替换掉 step-number 作为核心模型的设计。

11. 本次落地后的实现约束

本次重构后,核心运行路径已经按下面规则收敛:

  1. background、content、sidepanel 的消息协议只使用 EXECUTE_NODENODE_COMPLETENODE_ERRORSKIP_NODE
  2. 核心运行状态只使用 flowIdrunIdcurrentNodeIdnodeStatuses
  3. 账号运行历史的新记录使用 flowIdrunIdfailedNodeId,失败/停止状态使用 node:<nodeId>:failednode:<nodeId>:stopped
  4. sidepanel 渲染仍可以展示“第几项”的用户文案,但状态合并、按钮执行、跳过、恢复都以 nodeId 为主键。
  5. 邮件规则、source registry、driver command 已跟 node 对齐;验证码节点通过 mailRuleId 绑定,而不是通过固定步骤号绑定。
  6. 自动运行主循环使用 runAutoSequenceFromNodeGraph(startNodeId) 按当前 workflow 的 node 列表推进,不再通过数字序号、step++runAutoSequenceFromNodeOrder 驱动。
  7. 自动运行恢复、idle 重开、Plus/GPC/GoPay checkout 重建都以目标 nodeId 和实际前置节点为锚点;遇到稀疏节点图时不会再依赖不存在的虚拟数字步骤。

阶段 8 自检命令要求核心生产路径不得再命中旧协议和旧状态字段:

rg -n "EXECUTE_STEP|STEP_COMPLETE|STEP_ERROR|SKIP_STEP|STEP_STATUS_CHANGED" background.js background content sidepanel shared flows data
rg -n "currentStep|stepStatuses|setStepStatus|runAutoSequenceFromStep" background.js background sidepanel content shared flows data
rg -n "step\d+_failed|step\d+_stopped|step2_stopped|step7_stopped|step8_failed|step9_failed|step10_failed" background.js background
rg -n "runAutoSequenceFromNodeOrder|currentStartStep|startNodeOrder|while \(step <=|step \+= 1" background.js background

12. 后续新增节点的标准入口

同一 flow 新增节点时,按这个顺序开发:

  1. data/step-definitions.js 的当前 flow 定义里新增 node,并补齐 nodeIdtitlesourceIddriverIdcommandmailRuleIdnext
  2. background/steps/ 增加或复用节点执行器,并在 registry 构建处按 executeKey 注册。
  3. 如果节点需要页面执行,给对应 content driver 增加 EXECUTE_NODE command handler。
  4. 如果节点需要邮件验证码,在 flows/<flowId>/mail-rules.js 增加 flow-local rule,并通过 mailRuleId 绑定。
  5. 增加 node-first 测试,断言消息、状态、历史、UI 都只使用 nodeId

13. 后续新增完全不同 flow 的标准入口

新增完全不同 flow 时,不要改 OpenAI 的节点树来“塞进去”,而是新增 flow-local 定义:

flows/<flowId>/
  workflow.js
  sources.js
  mail-rules.js
  capabilities.js
  recovery.js

落地前必须自检:

  • 新 flow 是否可以不包含 OpenAI 私有节点,例如 Plus、手机号接码、OAuth。
  • sourceId / driverId 是否只服务当前 flow,不污染全局。
  • 邮件规则是否只从当前 flow definition 派生。
  • sidepanel 是否能仅通过 workflow nodes 渲染,不新增全局步骤 switch。
  • 自动运行、历史和日志是否都能用 flowId/runId/nodeId 定位。