跳转至

远程 / 大型 Vault:文件树全量递归导致「解析不出来」

状态:已修复(eager 产品目录 + 非产品浅层 / 按需 list + 扩展忽略名)
影响面:打开 Vault 时的左侧文件树(本地与远程同一套建树逻辑;远程 SFTP 下体感最重)
相关代码

  • src/lib/vaultloadVaultTree / listVaultDirChildren / shouldIgnoreTreeName / isEagerTreeRel / replaceTreeNodeChildren
  • src/components/sidebar/file-tree.tsx — 展开时触发懒加载、pending spinner
  • src/App.tsxonLoadDirChildren 合并子树
  • test/vault-tree.test.ts — 忽略规则、eager 判定、pending 合并
  • 产品约定:../frontend/shell.md §2.1;远程设计:../backend/remote.md §6

1. 问题现象

  1. 打开远程 Vault(SSH/SFTP)时,连接本身可能成功(catalog / 远程徽章正常),但左侧文件树长时间空白或一直 busy,像「解析不出来」。
  2. 同一目录若在服务器本机 ls / 本地挂载打开,往往仍可用,用户易误判为「远程路径错误」或「SFTP 坏了」。
  3. 失败时 refreshTree 可能 catch 后 setTree([]),或卡在数万次串行 remote_list 上直至超时。

2. 根因

2.1 建树是「打开时全量递归」,不是文档里的懒加载(主因)

设计写过文件树应「懒加载 list」,实现却是:

loadVaultTree(root)
  → 对每个目录 remoteList / readDir
  → 子目录递归,直到 depth > 12

每个目录 = 一次 Host remote_list(SFTP readdir),且 SFTP 侧多有锁、串行
远程延迟下:list 次数 × 单次 RTT 直接决定打开耗时。

2.2 真实 Vault 常「产品目录 + 巨量非产品树」混放

产品需要深扫的是:

根目录 用途
papers/ paper marker、组织夹、下载/精读图标
notes/ / plans/ 笔记与计划
.agents/ bundled / 用户 skills

但用户工作区根下还可能有完整代码仓、实验 run、依赖树等。旧实现会把这些一并递归进文件树。

在「论文 + 大型代码/实验树」混合的远程库上,按旧忽略规则模拟建树:

指标 量级(该复现库)
readdir / SFTP list ~1.4×10⁴
目录节点 ~1.4×10⁴
文件节点 ~4.5×10⁴
其中非产品树占比 list 次数的绝大部分(约 97%+)

产品侧 papers/ 一类通常只有数百次 list;真正拖垮打开的是深层非产品子树。

2.3 旧忽略集合过窄

旧规则大致只跳过:.git.agenteronode_modulestargetdist 以及多数点开头名。

仍会扫入(示例类):

  • 名称不以点开头的依赖/构建产物目录
  • 嵌套在非产品树里的包与 vendored 代码
  • 其它本应用从不需要展示的缓存/工具目录

点开头的 .venv 等虽会被「点目录」规则跳过,但非隐藏的巨型项目树仍全量进入递归。

2.4 远程 vs 本地体感差几个数量级

环境 同一套全量递归
服务器本机 scandir 整树可在 ~0.1 s 级完成(盘很快)
客户端经 SFTP 串行 list ~20–100 ms/次 估,整树约 数分钟~十余分钟

因此:不是路径无效,而是打开策略不可扩展。


3. 修复策略

3.1 两类目录

类型 根名(vault-relative 顶段) 打开时 展开时
Eager papersnotesplans.agents 全量递归(保留 paper marker / skill 语义)
Lazy 其它根下目录(代码、论文草稿夹等) 只 list 一层(文件 + 子目录壳) 子目录 childrenPending,展开再 list 一层

3.2 忽略名永不 list

TREE_IGNORE_NAMES + 点目录策略(保留 .agents.env.example)+ *.egg-info

  • VCS / Host 内部:.git.agentero、…
  • 依赖与缓存:node_modules.venvvenv__pycache__site-packages、各类 .*_cache、…
  • 编辑器 / 工具:.codex.idea.vscode、…

命中则不进入树、不发 list

3.3 UI

  • FileNode.childrenPending:未 list 的目录仍显示为可展开文件夹。
  • 展开时 listVaultDirChildrenreplaceTreeNodeChildren 合并。
  • 加载中可显示行内 spinner;失败 toast,可再次展开重试。

4. 修复前后数据(同一混合型远程 Vault)

方法:在服务器上用与前后端一致的忽略 / eager / 浅层规则模拟 walk(本地 scandir 计次数);远程耗时按 list 次数 × 实测单次 SFTP list 延迟 估算(同会话串行 list,约数十 ms/次量级)。

4.1 工作量

指标 修复前 修复后 变化
readdir / SFTP list ~13 600 ~390 约 35× 更少
目录节点(进树) ~13 600 ~400 约 34× 更少
文件节点(进树) ~44 700 ~2 200 约 20× 更少
待展开 pending 目录 0 少量(非产品一层下的子目录壳) 按需再 list

4.2 服务器本机 walk(无网络)

修复前 修复后
墙钟 ~0.11 s ~0.004 s
加速 约 25–30×

说明:盘上 list 不是瓶颈,对比的是调用次数与算法分支。

4.3 远程打开估算(串行 SFTP)

假设单次 list 修复前 修复后 加速
~20 ms ~4–5 min ~8 s ~35×
~50–60 ms(本机 CLI 批测量级) ~10–15 min ~20–25 s ~35×
~100 ms ~20+ min ~40 s ~35×

定性结论

  • 修复前:远程大库打开文件树常落在数分钟以上,UI 等同失败。
  • 修复后:同库打开落在数十秒内(主要成本仍是 eager 的 papers/ 等产品树);非产品根目录一层几乎立刻可见。
  • 收益与 list 次数比一致:约 35×

4.4 修复后 list 构成(典型)

部分 策略 占比(该复现)
产品 papers/ 等 eager 树 全量 绝大多数(~90%+ list)
其它根目录 每根 1 次浅层 极少
忽略名 0 0

因此:若仍慢,下一步优化应针对 papers/ 分层 / 批量 list,而不是再砍非产品树。


5. 验收建议

  1. 打开「产品目录 + 大型非产品子树」的远程 Vault:顶层与 papers/ 应在可接受时间内出现;非产品根可展开一层,不一次性扫尽深层。
  2. 忽略名(依赖、venv、.git 等)不出现在树中
  3. papers/ 的精简 Vault:行为与修复前一致(仍 eager 全量)。
  4. 展开 lazy 目录失败时 toast,树不整页清空;再次展开可重试。
  5. 单元测试:test/vault-tree.test.ts(忽略、eager、pending 合并)。

6. 后续(非本次)

  • papers/ 组织夹过深时仍可能数百次 list:可考虑 Host 批量 list_tree、或 org 层懒加载。
  • 刷新树时若保留已展开 lazy 子树,可减少重复 list(当前刷新仍按 loadVaultTree 重算 eager + 浅层)。
  • 打开进度 toast(「正在加载远程文件树…」)改善体感。

7. 决议记录

议题 决议
是否全树懒加载 :产品目录必须 eager(marker / skills)
非产品目录 打开 list 一层;子目录 pending,展开再 list
忽略策略 应用不需要的目录/缓存永不 list
文档是否写具体主机/路径 (可复现条件用结构描述,不写环境专有路径)