跳转至

Midscene AI 规划与元素定位缓存源码分析

分析版本:Midscene 1.10.5,基线提交 c4c33f8b 分析日期:2026-07-16 范围:Core TaskCacheaiAct 规划缓存、元素定位缓存、Web XPath 适配、缓存策略与失效机制

1. 结论摘要

Midscene 的缓存不是通用 HTTP 缓存或模型响应缓存,而是面向自动化执行的两类结构化缓存:

  1. 规划缓存(plan cache):以 aiAct prompt 和 action context 为键,保存模型生成的 YAML workflow。命中后跳过本次规划模型调用,直接执行缓存的步骤。
  2. 定位缓存(locate cache):以元素描述 prompt 为键,保存由页面接口生成的 element cache feature。当前实际实现是 Web XPath;命中后先在当前 DOM 中验证 XPath,再跳过视觉定位模型调用。

查询类能力 aiQueryaiBooleanaiAssertaiWaitFor 等不缓存返回值,因为结果应反映当前页面状态。

对 Android 最重要的结论是:

  • Android 可以使用 规划缓存,前提是当前 planning adapter 允许缓存。
  • Android 当前不能使用 元素定位缓存,因为 AndroidDevice 没有实现 cacheFeatureForPoint()rectMatchesCacheFeature()
  • 命中规划缓存不代表完全不调用 AI。缓存 YAML 中的 aiTapaiInput 等仍可能触发 Android 视觉定位。

缓存文件是明文 YAML,没有 TTL、应用版本、页面 URL、设备类型或模型版本维度,也没有跨进程文件锁。它适合稳定流程加速,但不能当成长期正确性的保证。

2. 总体架构

flowchart TD
    Config["Agent cache 配置"]
    Normalize["validate + processCacheConfig"]
    TaskCache["TaskCache"]
    File["midscene_run/cache/id.cache.yaml"]
    Act["agent.aiAct"]
    PlanMatch["matchPlanCache"]
    CachedYaml["执行缓存 YAML"]
    Planner["规划模型"]
    PlanWrite["写入 plan record"]
    Locate["TaskBuilder Locate"]
    LocateMatch["matchLocateCache"]
    Validate["rectMatchesCacheFeature"]
    LocateAI["视觉定位模型"]
    Feature["cacheFeatureForPoint"]
    LocateWrite["写入 locate record"]

    Config --> Normalize --> TaskCache
    File --> TaskCache
    TaskCache --> File
    Act --> PlanMatch
    TaskCache --> PlanMatch
    PlanMatch -->|可用| CachedYaml
    PlanMatch -->|未命中或不可用| Planner --> PlanWrite --> TaskCache
    CachedYaml --> Locate
    Planner --> Locate
    Locate --> LocateMatch
    TaskCache --> LocateMatch
    LocateMatch --> Validate
    Validate -->|有效| Locate
    Validate -->|失效| LocateAI
    LocateMatch -->|未命中| LocateAI
    LocateAI --> Feature --> LocateWrite --> TaskCache

核心源码:

3. 配置入口与策略

3.1 Agent 配置

Core 类型定义为:

1
2
3
4
5
type CacheConfig = {
  id: string;
  strategy?: 'read-only' | 'read-write' | 'write-only';
  cacheDir?: string;
};

直接创建 Agent 时必须显式提供 id:

1
2
3
4
5
6
const agent = new AndroidAgent(device, {
  cache: {
    id: 'rewardwall-login',
    strategy: 'read-write',
  },
});

validateAgentCacheInput() 会拒绝:

  • 直接使用 cache: true
  • 对象配置没有 id。
  • 空白 cacheDir
  • 非字符串或未知 strategy。

YAML Player 和 Playwright Fixture 会先根据文件名或测试 id 补齐 id,因此它们可以向用户暴露 cache: true。这一层自动补齐发生在 Agent 构造之前,Core Agent 自身仍要求明确 id。

3.2 新旧配置优先级

processCacheConfig() 的优先级是:

  1. 显式 cache: false:禁用。
  2. 显式 cache object:使用新配置。
  3. cache: true:由 CLI/Fixture 上层补 id;直接 Agent 会在验证阶段拒绝。
  4. 未配置 cache:只有同时设置旧 cacheIdMIDSCENE_CACHE=true 才启用兼容模式。
  5. 其他情况:不创建 TaskCache

cacheId + MIDSCENE_CACHE 仅用于向后兼容,新代码应使用 cache: { id }

3.3 三种策略

策略 启动时读文件 自动使用缓存 自动写文件 手动 flushCache()
read-write 可以
read-only 否,只更新内存 可以,并会写盘
write-only 可以
stateDiagram-v2
    [*] --> Disabled: cache=false / 未配置
    [*] --> ReadWrite: strategy=read-write 或省略
    [*] --> ReadOnly: strategy=read-only
    [*] --> WriteOnly: strategy=write-only

    ReadWrite --> ReadWrite: 命中、更新、立即写盘
    ReadOnly --> ReadOnly: 命中、仅内存更新
    ReadOnly --> Persisted: 手动 flushCache
    WriteOnly --> WriteOnly: 不读取,每次调用 AI 并写盘
    Disabled --> Disabled: 每次走正常 AI 流程

read-only 不是操作系统级只读。自动更新不会写文件,但公开的 agent.flushCache() 会直接调用 flushCacheToFile(),因此手动 flush 可以覆盖原文件。

write-only 启动时完全不读取已有文件,内存从空 caches 开始;第一次产生记录时会用本次内存内容重写目标文件。它适合重建缓存,不适合在已有文件上做追加式并发采集。

4. 缓存文件和数据结构

4.1 默认路径

默认路径由 getMidsceneRunSubDir('cache') 生成:

<cwd>/midscene_run/cache/<cache-id>.cache.yaml

可以通过以下方式改变位置:

  • MIDSCENE_RUN_DIR:改变整个 Midscene 运行目录。
  • cache.cacheDir:只改变当前 TaskCache 目录。
  • TaskCache 内部构造参数 cacheFilePath:测试和底层调用可直接指定完整文件路径。

浏览器和 Worker 环境没有文件系统,cacheFilePathundefined。此时可以在当前实例内维护内存记录,但无法跨刷新/进程持久化。

4.2 YAML 格式

midsceneVersion: 1.10.5
cacheId: rewardwall-login
caches:
  - type: plan
    prompt: 登录并进入首页
    yamlWorkflow: |-
      tasks:
        - name: 登录并进入首页
          flow:
            - aiTap: Google login button
            - aiTap: test account
  - type: locate
    prompt: Google login button
    cache:
      xpaths:
        - /html/body/main/button[normalize-space()="Google login"]

顶层只有:

  • midsceneVersion
  • cacheId
  • caches 数组。

没有创建时间、最后使用时间、应用版本、Git commit、URL、包名、设备型号、模型配置或过期时间。

写盘时 plan records 会排在 locate records 前面;同类型记录保持原相对顺序。内存数组不会因为写盘排序而改变,但下次加载会按文件顺序重新建立顺序。

4.3 规划记录

1
2
3
4
5
interface PlanningCache {
  type: 'plan';
  prompt: TUserPrompt;
  yamlWorkflow: string;
}

prompt 可以是普通字符串或带图片的 multimodal prompt。匹配使用 isDeepStrictEqual(),图片 URL、选项或上下文任一字段变化都会导致 miss。

4.4 定位记录

1
2
3
4
5
interface LocateCache {
  type: 'locate';
  prompt: TUserPrompt;
  cache?: ElementCacheFeature;
}

ElementCacheFeature 是平台开放的通用 object。当前 Web 实现保存 xpaths: string[]。老格式顶层 xpaths 会在命中时迁移到 cache.xpaths,下一次更新时清除旧字段。

5. 缓存匹配规则

5.1 精确 prompt 匹配

matchCache() 从文件加载时的原始记录范围内顺序查找:

1
2
3
record.type === requestedType
AND isDeepStrictEqual(record.prompt, currentPrompt)
AND record 本次运行尚未消费

它不比较:

  • 当前页面截图或 DOM hash。
  • URL、Activity、包名或页面 route。
  • 模型名称和模型版本。
  • Git commit、APK 版本或测试环境。

因此 cache id 必须由调用方承担“场景隔离”责任。同一句“点击提交按钮”如果在多个页面共用同一 cache id,规划缓存可能在错误页面直接执行。

5.2 action context 是规划键的一部分

aiAct 先调用 buildPromptWithContext()。存在 context 时,实际键变成:

1
2
3
4
Context for this request:
<trimmed context>

<task prompt>

context 的空白会 trim,但正文、换行和大小写仍精确参与匹配。定位 prompt 不自动附加 aiActContext,它使用具体 Locate task 的 prompt。

5.3 一条记录每次实例只能消费一次

matchedCacheIndices 记录 type + prompt + index。同一条记录在一个 TaskCache 实例中只能命中一次。

如果同一流程中同一个 prompt 出现三次,缓存文件需要按发生顺序保存三条记录;只有一条记录时,第一次命中,后两次 miss。这样可以让列表中多个“删除”按钮分别对应不同 XPath,而不是无限复用第一条记录。

5.4 本次新写记录不能立即命中

构造时保存 cacheOriginalLength。匹配循环只扫描 [0, cacheOriginalLength),本次运行追加的记录不会被本次运行再次读取。

这防止以下反馈环:

本次 AI 产生结果 -> 立即命中刚写的结果 -> 隐藏同一运行中的状态变化/错误

新记录只有下一个 Agent/TaskCache 实例重新从文件加载后才可命中。

6. 规划缓存链路

6.1 命中与执行

sequenceDiagram
    participant Caller as agent.aiAct
    participant Cache as TaskCache
    participant Runner as TaskExecutor
    participant Model as Planning Model
    participant File as cache.yaml

    Caller->>Cache: matchPlanCache(prompt + context)
    alt 命中且 YAML 有非空 flow
        Cache-->>Caller: cacheUsable=true
        Caller->>Runner: loadYamlFlowAsPlanning()
        Caller->>Runner: runYaml(cached workflow)
        alt 缓存流程成功
            Runner-->>Caller: 完成,不调用规划模型
        else 缓存流程失败
            Runner-->>Caller: throw
            Caller->>Model: 从当前页面重新规划
            Model-->>Caller: fallback flow/result
            Caller->>Cache: 将原记录更新为 flow: []
            Cache->>File: 写盘
        end
    else 未命中、禁用或 YAML 不可用
        Caller->>Model: 完整规划
        Model-->>Caller: yamlFlow
        Caller->>Cache: append 或更新原记录
        Cache->>File: 写盘
    end

6.2 何时允许 plan cache

必须同时满足:

  1. Agent 已创建 TaskCache
  2. 当前调用没有 cacheable: false
  3. planning adapter 的 cacheEnabled=true
  4. 缓存记录未在本实例中使用过。
  5. YAML 可以解析,并且至少一个 task 的 flow 非空。

标准 planning adapter 默认允许缓存;custom planning adapter 默认关闭,除非 adapter 明确启用。AutoGLM 和 UI-TARS 当前显式配置 cacheEnabled=false,见 auto-glm/adapter.tsui-tars/adapter.ts

6.3 不可用 YAML

matchPlanCache() 会把以下记录标为 cacheUsable=false

  • yamlWorkflow 为空或只有空白。
  • YAML 无法解析。
  • 没有 tasks flow。
  • 所有 flow 都为空数组。

记录仍返回给调用方,并被标记为已消费。正常规划成功后,updateOrAppendCacheRecord() 通过该 MatchCacheResult.updateFn 原地修复记录,不会追加重复项。

6.4 缓存 workflow 执行失败

缓存 YAML 可能执行了一半后失败,例如“关闭偶现弹窗”步骤在本次不存在。此时页面已经被前半段动作改变,fallback planning 得到的只是**从中间状态继续执行**的 flow,不能安全作为下次从初始页面执行的完整缓存。

因此 Agent.aiAct() 不保存 fallback flow,而是把命中的 plan record 改成空 flow。下一次新实例读取时,它被视为不可用并重新生成完整计划。

这是有意的保守策略,不是“缓存更新失败”。

该修复动作发生在 fallback planning 正常返回之后。如果缓存 YAML 失败,并且随后的 fallback planning 也直接抛错,aiAct() 会在执行到 cache update 前退出,旧 plan record 仍可能留在文件中;下一次新 Agent 仍可能再次尝试它。

7. 元素定位缓存链路

7.1 定位优先级

TaskBuilder 的定位顺序是:

  1. planning 直接返回的 bbox,且未启用 deepLocate。
  2. 用户显式传入的 XPath。
  3. 已缓存的 element feature。
  4. AI locate。
flowchart TD
    Start["Locate task"] --> Plan{"Plan 有 bbox 且非 deepLocate?"}
    Plan -->|是| PlanHit["使用 plan bbox"]
    Plan -->|否| XPath{"用户传入 xpath 可解析?"}
    XPath -->|是| XPathHit["使用显式 xpath"]
    XPath -->|否| Cache{"有未消费 locate record?"}
    Cache -->|否| AI["调用 AI locate"]
    Cache -->|是| Validate["rectMatchesCacheFeature"]
    Validate -->|有效| CacheHit["使用缓存 rect"]
    Validate -->|异常或无 rect| AI
    AI --> Feature{"cacheFeatureForPoint 可用且非空?"}
    PlanHit --> Feature
    Feature -->|是| Write["更新/追加 locate record"]
    Feature -->|否| Done["只返回定位结果"]
    Write --> Done
    XPathHit --> Done
    CacheHit --> Done

7.2 Web XPath 如何生成

Web 页面定位成功后,cacheFeatureForPoint()

  1. 把命中元素中心点转换为页面逻辑坐标。
  2. 可选调用 AiJudgeOrderSensitive() 判断描述是否依赖顺序,例如“第二个按钮”。
  3. 在页面注入 element inspector。
  4. 调用 getXpathsByPoint() 生成一个或多个候选 XPath。
  5. 过滤空值并保存为 { xpaths }

首次写定位缓存不一定减少模型调用:判断 order-sensitive 本身可能产生一次额外 AI 请求。收益主要体现在后续运行。

7.3 命中时验证

rectMatchesCacheFeature() 按顺序尝试候选 XPath:

  1. 在当前 DOM 中查询 XPath。
  2. 读取 element info 和 rect。
  3. 第一个有 rect 的候选即命中。
  4. 全部失败则抛错,Core 回退 AI locate。

返回的是逻辑坐标 rect,Core 再按截图缩放比例转换为截图坐标。缓存本身不保存旧屏幕绝对坐标。

7.4 缓存失效后修复

有两类失效:

  1. 验证时失效:XPath 找不到元素。当前 Locate task 立即调用 AI,并通过已有 MatchCacheResult 原地更新该记录。
  2. 验证成功但动作结果错误:XPath 找到了一个 rect,但点错元素或未完成目标,随后触发 replanning。

第二类更容易污染缓存。Midscene 的处理流程是:

stateDiagram-v2
    [*] --> Loaded: 从 YAML 加载 locate record
    Loaded --> Consumed: prompt 精确匹配
    Consumed --> Validated: XPath 返回 rect
    Validated --> Stable: 动作成功且流程继续
    Validated --> Stale: 动作失败或目标未完成并 replan
    Stale --> Relocated: 同 prompt 再次走 AI locate
    Relocated --> Replaced: 原 index 原地替换
    Replaced --> [*]: 写回 YAML
    Loaded --> Invalid: XPath 验证失败
    Invalid --> Replaced: AI locate 后通过 updateFn 更新

invalidateFailedCacheHitLocates() 在 replan 前扫描本轮 hitBy.from === Cache 的 Locate task,并调用 markLocateCacheStale()。下一次同 prompt 的 AI locate 不会追加到末尾,而会替换刚才被拒绝的 index,避免错误老记录永远排在前面反复命中。

正常重复 prompt 不会被误替换。只有“已经消费并显式标记 stale”的记录才允许原地替换;其他 miss 一律 append。

7.5 Android 为什么没有 locate cache

Core 的定位缓存是能力探测式设计,要求设备 interface 实现:

cacheFeatureForPoint(point): ElementCacheFeature
rectMatchesCacheFeature(feature): Rect

当前仓库中只有 Puppeteer 页面和 Chrome Extension 页面实现这两个方法。Android 没有稳定 DOM/XPath,也没有提供其他 element feature,因此:

  • matchElementFromCache() 发现缺少 rectMatchesCacheFeature 后返回 miss。
  • 定位成功后缺少 cacheFeatureForPoint,不会写 locate record。
  • Android cache YAML 通常只有 plan records。

如果未来 Android 要支持定位缓存,需要设计 UIAutomator resource-id、accessibility node path、文本/类名组合及二次验证,不能直接复用 Web XPath。

8. cacheable 的作用域

cacheable: false 是单次 API 级开关:

await agent.aiAct('完成登录', { cacheable: false });
await agent.aiTap('提交按钮', { cacheable: false });

aiAct

  • 不读取 plan cache。
  • 不写 plan cache。
  • 该值继续传给本轮生成的 Locate subtasks,使其不使用/写入定位 feature。

对单独 locate/action:buildDetailedLocateParam() 默认 cacheable=true,显式 false 后 matchElementFromCache() 不使用记录,TaskBuilder 也不写新 feature。

当前实现会在 cacheable guard 之前调用 matchLocateCache(),所以一条记录可能被标记为“本实例已消费”,随后因 cacheable=false 没有实际使用。这意味着:

  • 同一 Agent 后续相同 prompt 不能再命中该条记录。
  • cleanUnused 可能把它视作本次使用过的旧记录并保留。

这不影响当前调用绕过缓存的结果,但属于缓存统计和同实例后续行为的实现边界。

查询和断言任务没有对应 cache read/write 分支;即使 Agent 开启缓存,它们仍调用模型。

9. 自动写入、手动清理和版本兼容

9.1 自动写入

read-write/write-only 模式每次 append、update 或 stale replacement 都调用 flushCacheToFile()

  1. 确保目录存在。
  2. 复制并排序 records。
  3. yaml.dump({ lineWidth: -1 })
  4. writeFileSync() 重写整个文件。

这不是增量 append,也不是临时文件加 rename 的原子写入。

9.2 手动清理

await agent.flushCache({ cleanUnused: true });

清理规则:

  • 保留本次运行命中的原始记录。
  • 保留本次运行新增的记录。
  • 删除从文件加载但本次未命中的记录。
  • write-only 因不读取缓存而跳过清理。
  • read-only 允许通过手动 flush 清理并覆盖文件。

清理是显式操作,不会在 Agent destroy 时自动执行。没有 TTL、LRU 或按文件大小自动淘汰。

9.3 版本检查

加载 YAML 时会检查 midsceneVersion

  • 低于 0.16.10 且不是 beta 字符串时拒绝使用。
  • .json 文件存在时给出迁移警告。
  • YAML 解析、字段读取或 semver 检查异常时整体按 cache miss 处理。
  • 加载成功后只把内存中的 midsceneVersion 更新为当前版本。

它没有严格 schema migration,也不拒绝高于当前版本的文件。版本通过不代表 plan/action schema 一定兼容,真正执行失败时仍依赖 plan fallback。

10. 报告和调试可观测性

命中缓存的 task 会设置:

task.hitBy.from = "Cache"

规划缓存还保存 hitBy.context.yamlString。Report App 在侧栏显示 Cache 标签,在详情页展示命中的 YAML,见 sidebar/index.tsxdetail-side/index.tsx

调试日志:

DEBUG=midscene:cache:* <your-command>

重点日志包括:

  • cache file 是否加载成功和记录数量。
  • prompt 是否命中、命中 index。
  • plan flow 是否为空/无效。
  • element feature 是否验证成功。
  • stale record 是否被标记和替换。
  • 自动/手动写盘是否成功。

报告只能证明某个 task 标记为 Cache,不能证明整个 aiAct 完全没有模型调用。应同时检查 task usage 和 locate subtasks。

11. 性能与 Token 收益

11.1 能省掉什么

命中类型 通常省掉 仍可能发生
plan cache planning 模型调用 YAML 内的 locate、query、assert、动态 replan
locate cache locate 视觉模型调用 DOM XPath 验证、动作执行、后续 planning
两者都命中 planning + locate 调用 页面操作和状态等待

Android 只有 plan cache,因此收益取决于缓存 YAML 的内容。若每一步仍是自然语言 aiTap,定位 token 仍会消耗;若 planning 结果自带可直接使用的 bbox,但页面布局变化,坐标复用又有稳定性风险。

11.2 本地开销

  • 每次自动更新都同步序列化并重写整个 YAML 文件。
  • 缓存记录很多时,writeFileSync() 会阻塞当前 Node 事件循环。
  • 匹配是从头线性扫描,复杂度约为 O(record count)。
  • prompt object 使用 deep strict equality,图片型大对象会增加比较成本。

当前缓存文件一般较小,所以实现偏简单。平台化到大量 Journey、多个设备时,应避免所有任务共享一个巨大 cache id。

12. 并发和 CI 边界

TaskCache 没有:

  • 文件锁。
  • 进程间合并。
  • revision/CAS 检查。
  • 原子临时文件 rename。
  • 并发写队列。

两个 Agent/Runner 同时使用同一路径时:

sequenceDiagram
    participant A as Runner A
    participant B as Runner B
    participant F as cache.yaml

    A->>F: 启动时读取版本 V0
    B->>F: 启动时读取版本 V0
    A->>A: 追加 record A
    B->>B: 追加 record B
    A->>F: writeFileSync(V0 + A)
    B->>F: writeFileSync(V0 + B)
    Note over F: 最终 A 可能丢失,last writer wins

CI 建议:

  1. cache id 至少包含项目、Journey/测试用例和稳定环境标识。
  2. 并行 job 不写同一个 cache file。
  3. 正式回归使用 read-only,缓存生成使用独立 write-only job。
  4. 生成 job 完成后,通过 MR 审核 cache diff,再进入正式分支。
  5. 不要让失败测试自动覆盖共享缓存;只在测试通过后手动 flush/发布 artifact。

13. 正确性与安全边界

13.1 无页面环境维度

缓存键主要是 prompt。页面变化依赖“执行失败后回退”被动发现,因此存在两类风险:

  • plan 在错误页面仍能成功执行一部分副作用,然后才失败。
  • XPath 在新 DOM 中仍能找到 rect,但语义已经变了。

高风险操作如支付、删除、提现不应只依赖旧 plan cache。至少需要执行前后断言、环境隔离和测试账号。

13.2 明文敏感信息

YAML 可能包含:

  • 用户 prompt 和 action context。
  • aiInput 的输入值。
  • 页面文本和 XPath。
  • 测试账号或业务数据。

文件没有加密或脱敏。不要把真实密码、token、验证码、支付信息写进可缓存 prompt/workflow。

13.3 缓存文件具有“可执行配置”属性

plan record 的 YAML 会直接交给 runYaml()。如果项目支持 custom action,恶意修改缓存可能触发具有副作用的动作。缓存文件应像测试脚本一样接受代码审查,不能无条件信任外部 artifact。

13.4 路径输入必须可信

cacheId 会替换 Windows 文件名非法字符和空格,但源码明确保留 /\,也没有移除 ..。配合 path.join() 时,包含 ../ 的 id 可以把文件写出预期 cache 子目录。

因此:

  • 不要把用户输入、仓库名、分支名原样作为 cache id。
  • 平台侧应限制 id 为安全字符集,例如 [A-Za-z0-9._-]+,并拒绝 ..
  • cacheDir 应由服务端配置,不允许普通测试用户提交任意绝对路径。

14. 已知局限与适用建议

14.1 已知局限

  • 无 TTL 和自动环境指纹。
  • 无跨进程并发安全。
  • 文件整包同步重写。
  • Android/iOS 等无 DOM 平台缺少 locate cache adapter。
  • custom planner 默认不支持 plan cache。
  • fallback 只能在执行时发现陈旧 plan,可能已经产生部分副作用。
  • 缓存 plan 和 fallback planning 连续失败时,旧 plan 可能来不及写成空 flow。
  • cacheable=false 的 locate record 仍可能被内部 bookkeeping 消费。
  • 版本检查只提供最低版本门槛,不是完整 schema 兼容保证。

14.2 推荐场景

  • 页面结构和数据稳定的回归测试。
  • 重复执行的长 aiAct 流程。
  • Web DOM 稳定且 XPath 可验证的元素定位。
  • CI 中经过审核的只读缓存。

14.3 不推荐场景

  • 生产真实账号的支付、提现、删除操作。
  • 页面高度动态、A/B 实验频繁或多租户结构不同。
  • 多 Runner 并发写同一 cache id。
  • 依赖 Canvas/WebGL/跨域 iframe/closed Shadow DOM 的 Web 元素定位。
  • 期望缓存后完全不配置 AI 服务;缓存失效和未缓存任务仍需要模型。

15. 测试覆盖

Core 和 Web Integration 已覆盖多数关键行为:

当前明显缺少:

  • 多进程/多 Agent 同文件并发写测试。
  • 原子写入中途进程退出恢复测试。
  • cache id 路径穿越拒绝测试;当前行为是保留 path separator。
  • Android plan cache 端到端 token/耗时基准。
  • 应用版本、模型版本变化后的自动失效策略测试。

16. 关键源码索引

模块 文件 责任
缓存主体 task-cache.ts YAML、匹配、写入、stale、清理
配置校验 cache-config.ts id、strategy、cacheDir
兼容配置 utils.ts 新 cache 与旧环境变量优先级
规划缓存 agent.ts plan match、YAML 执行和 fallback
action/replan tasks.ts 缓存定位失败后的 stale 标记
定位缓存 task-builder.ts 定位优先级、feature 读写
feature 验证 agent/utils.ts cache feature -> rect
Web XPath base-page.ts XPath 生成和恢复
Web cache helper cache-helper.ts XPath 清洗、顺序敏感判断
planning 策略 planning.ts adapter cacheEnabled 默认值
报告标记 sidebar/index.tsx Cache 标签