Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-29-directory-picker-adaptive-default.zh.md
T

5.6 KiB
Raw Blame History

Agent Note: 目录选择交互的自适应默认值

Status: implemented

English | 中文

问题

目录选择 seam把交互形态做成了 cordis.yml 的切换点,但随附的组合仍必须固定一个后端:处处用 -browse 意味着本地操作者永远得不到 OS 选择器,处处用 -native 则弄坏所有远程部署。正确的默认值取决于只有运行中的宿主才知道的事实——服务器绑定在哪里、进程是否经 SSH 启动、是否存在显示会话——因此没有哪一静态行对所有部署都正确。

决策

第三个同级包 dsh-host-directory-picker-auto:一个只有 node 半侧的选择器,不持有任何选取代码,也没有 UI。它的 apply 在启动时恰好采样一次宿主事实——从注入的 httpServer 读绑定宿主(新增的 host getter 与既有的 port 对称)、SSH_CONNECTION/SSH_TTY、平台、DISPLAY/WAYLAND_DISPLAY、以及对 Linux 选择器二进制(zenity/kdialog)的一次 PATH 探查——经由一个导出的纯函数判定,再用 ctx.loader.create({name}) 把选中的双面后端挂进 Loader 的内存根树;该 effect 的 disposer 会移除该条目并汇入后端 fiber 的拆卸(单靠 remove() 只是启动拆卸),因此卸载选择器要到后端静止之后才落定。native 要求全部“有人值守且可服务”信号:回环绑定 ∧ 无 SSH 标记 ∧ native 后端能驱动的显示会话——darwin/win32 上视为存在,linux 上要求 DISPLAY/WAYLAND_DISPLAY 外加一个选择器二进制,其余平台一律不成立(native 后端恰好支持 darwin/win32/linux)。任何含糊情形都判定为处处可用的 browse。apps/cli 现在把 -auto 挂为它的 directory-picker 行;直接组合 -native 或 -browse 仍是固定交互的方式。

条目级挂载之所以是承重机制:client 模块表(dsh-client-modules)基于 internal/plugin 对 Loader 条目做响应式协调,因此以真实条目挂载的后端,其 browser half 被发现的方式与配置行完全相同——seam 的“一行同时换两面”不变式在自适应下依然成立,且没有一行重复的 client 代码。开发环境的 HMR 行(AppCLIEntry)是该机制的先例。瞄准根树很关键:根树的 write() 是 no-op,因此判定出的行绝不会被持久化回 cordis.yml(Include 子树会写回)。

曾考虑的替代方案

  • 在 AppCLIEntry 里做启动胶水判定(随附两行并带静态 disabled,由 --directory-picker=auto|native|browse 标志修补 disabled)。可行——PatchOptions 能修补元数据,模块扫描也会跳过禁用行——但把决策留成应用私有,此后每个组合都要重新实现;选择器插件让任何 cordis.yml 都获得同样的一行自适应。只有当某个部署需要不改自己的 yml 就强制指定后端时,才重新引入该标志。
  • 合并成一个按调用分支的插件(client 先试 pick,收到 directory-picker-unavailable 再回退到浏览对话框)。否决:client 得把两套流程装进同一个 bundle——bundle 纯净门禁禁止跨插件的值导入,jscpd 禁止复制对话框——而且按调用探测让 browse 宿主每次打开都付出一次注定失败的 RPC。
  • 复活 wire 广播,让两套 client 流程都挂载并按宿主的 kind 分支。否决:推翻 seam Agent Note 的那次删除,却服务不了任何选择器尚未服务的消费方,还与 single 目录流洞相冲突。
  • 按连接自适应(同一台服务器,回环浏览器用 native、远程浏览器用 browse)。延期:需要按客户端的能力对象、上述广播,以及同时挂载两套流程;今天没有部署同时服务两种操作者形态。

后果

  • 随附的 web GUI 开箱即自适应:有人值守的本地宿主 → OS 选择器;SSH 启动、全网卡绑定、无头宿主、不支持的平台,或没有选择器二进制的 Linux → 应用内浏览器。探测是从启动上下文推断操作者位置,而任何启动侧信号都无法证明这一点:脱离的 tmux 会话会丢失 SSH_*;非 Aqua 的 darwin 进程仍被算作有显示;而 ssh -L 形态(在工作站本地启动、之后经转发端口访问,从 127.0.0.1 到达)会判定 native,把选择器弹在无人值守的工作站上——即便按连接自适应也修不了最后这一情形。错误的 native 选择会退化为后端既有的可重试失败对话框;处于这些形态的部署直接组合 -browse。
  • 选择器按运行时字符串(已导出的 BACKEND_PACKAGES)挂载后端,yml 行扫描看不到这一点;因此 verify-cordis-config 要求每个挂载 -auto 的组合把两个后端都声明为依赖,使无密钥的 Linux CI(它永远只会判定出 browse)无法掩盖被丢掉的 -native 依赖。随附树的 web e2e/快照通道(apps/web/tests/scaffold.ts)以 disable+insert 补丁固定 -browse——其 golden 是交互特定的,绝不能依赖运行该套件的宿主。
  • 每次启动只判定一次,维持 seam 的能力稳定性契约;按连接的形态在有部署提出需求前仍不在范围内。
  • 同时挂载选择器和某个后端行会大声失败(重复的 directoryPicker 服务;single 洞中的重复流程)。
  • host 类型检查聚合现在引用两个后端项目(仅声明,node 入口不携带 client 合并),使选择器的 REAL-composition 测试能挂载它们——与 client 聚合对 webserver 的引用互为镜像。