Files
deepseek-harness/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.zh.md
T
2026-07-31 18:37:34 +08:00

7.8 KiB
Raw Blame History

Agent Note: One route to add a Workspace

Status: implemented

English | 中文

Problem

两处 Workspace 表层——侧边栏区头的 + 与会话主视觉区的 chip——都提供了两条获得 Workspace 的路径:打开本地文件夹… 拉起组合的目录流程,新建工作区 接收一个名称并创建 <workspaceRoot>/<name>。两者重叠:浏览占用者自带 新建文件夹 能力,因此"选一个目录"本就覆盖了"建一个目录"。两个入口意味着同一结果有两套词汇、一个自带重名规则的名称对话框,以及一个操作者既看不到也选不了的创建位置。

删掉较弱的那个入口后,侧边栏区头只剩一个动作,于是引出了本 Note 一并裁定的展示问题:只有一行的浮层应该长什么样。

Decision

添加 Workspace 只有一条路径:通过组合的目录流程选一个宿主机目录,新建的或已存在的都可以。入口是 menu.addWorkspace("添加工作区…" / "Add workspace…");按名称创建的对话框及其 create.*/menu.createWorkspace/workspace.new 文案全部删除。标签命名的是结果而非机制,因为它现在是通往该结果的唯一一扇门——找"新建"的用户必须能找到它。

菜单的存在是为了在多个目标之间消歧。 当只剩添加这一个入口时——仅添加的侧边栏表层,或列表为空的主视觉区——锚点手势就是该动作:直接拉起流程,不渲染浮层。只有一行的浮层多花一次点击,却没有任何可选项。这条规则是一个谓词(addIsTheOnlyEntry)覆盖两处表层,而不是逐表层特判。

由该规则派生出两条边界,它们同属这个决定:

  • 列表为空只有在基线落地后才算最终结果。 phase 仍为 pending 时,主视觉区保留菜单与加载状态,而不是跳进一个即将到达的 workspace 会使其变得多余的流程。仅添加表层不列任何东西,因此从不等待。
  • 目录流程的洞没有占用者时,就没有任何可添加的手段。 此时侧边栏区头干脆不渲染按钮,而不是留下一个点了没反应的按钮;主视觉区的菜单则继续作为选择器工作,列出已有内容——而当同样没有内容可列时,它什么也不弹:空浮层会宣称一个并不存在的选择。这是 seam 文档化的无流程默认行为走到它的结论:占用者不在,唯一的创建能力也就不在。主视觉区的锚点 chip 归 ui-conversation 所有,因此本包能压掉浮层,却无法隐藏该 chip。

直接拉起的这条路径同样遵守菜单项声明的 busy 规则:某次选取仍在接纳中(flowBusy)时,锚点手势会被按住,正如该菜单项会被禁用,从而使迟到的结果无法抢开第二个流程。

WorkspaceCreateFlow 现更名为 WorkspacePickFlow,其 createOnly prop 更名为 addOnly;注入的 createWorkspace 从 { name } | { path } 收窄为 { path }。

Wire and CLI residue

Host 侧的 workspace.create 仍接受 { name },dsh web --workspace-root 也仍在为它提供目标目录,但已没有任何产品表层会走到它们。把名称送到 wire 的客户端一段同样如此:WorkspaceCreateInput、WorkspacesService.create 的 { name } 分支、intentName 的名称分支,以及 manager 中"workspaceRoot 下的 name"这一契约。apps/cli/README.md 及其中文对照本也仍把 --workspace-root 记为"创建具名 Workspace"。这一整套都在 packages/host/apiproxy/src/api-proxy.ts 的调用点标记为待删除,并留给后续改动:它横跨 backend、客户端 seam 与 CLI 面,有各自的 reviewer 和各自的测试波及面(api-proxy workspace 套件、runtime workspace 套件、配置目录),而本决定中阻塞发布的部分是 UI。

Testing

connectFreshWorkspace——所有 web e2e 场景启动时都会走的辅助函数——会预先备好 <root>/workspace,再经对话框的路径编辑器接纳它,因此产出的会话 cwd 与按名称创建时完全一致,场景 golden 保持有效。选择预先备好而不是在对话框内新建,是为了让该辅助函数在一个场景可能发生的多次连接之间保持幂等(第二次创建同名文件夹会失败,而创建对话框会在失败时把流程停在原地)。在选择器内新建文件夹——同一条路径的另一半——由 workspace-management.e2e.ts 覆盖,它承担针对性覆盖:在对话框自己创建的文件夹上添加两个 workspace、接纳 basename 相同的不同目录并保持彼此独立、在另一个目录上复用已删除的标题、以及浏览对话框的 aria golden。

smoke-real.e2e.ts 是唯一启动未打补丁的出厂配置树的场景,其中 -auto 行会按宿主机解析;它现在通过 --config overlay 钉死 -browse,使开发机的显示环境无法决定选择器是否可被驱动。

Alternatives considered

保留 打开本地文件夹… 作为标签。 否决:合并后该入口既能打开也能创建,用机制命名会恰好对那些入口被我们删掉的用户隐藏创建这一半。反方理由——"本地"二字有效区分了浏览器所在机器与 harness 所在机器——在下一步就由对话框自身的标题和面包屑回答了。

保留双入口菜单,让 新建工作区 也打开同一个流程。 否决:同一动作两个标签正是我们被要求消除的混淆,而不是它的缩小版。

为了与主视觉区菜单保持一致而保留只有一行的浮层。 否决:不提供选择的浮层是一次浪费的点击,读起来像半成品。这里要一致的是规则(有菜单 ⇔ 存在选择),不是控件。

为将来可能新增的入口(克隆仓库、远程目录)保留菜单壳。 否决,依据"require a current owner and need":这样的入口目前并不存在,而等它到来时再恢复菜单,比现在就发一个空壳的改动更小。

在同一改动中删除 wire 的按名称创建分支。 本 PR 否决:那是 backend/CLI 面,reviewer 不同、测试波及面更广,而紧急的决定是 UI。见 residue 一节——它是被标记了,不是被遗忘了。

在 e2e scaffold 中经 host 注册 workspace,而不驱动对话框。 否决:那会让全部 15 个场景与选择器解耦,整条 lane 将无法证明幸存的这条路径能走到可用的 composer。现在每个场景都会走真实对话框来接纳自己的目录;只有"新建文件夹"那一半集中在一个场景里,因为处处重复只会让共享辅助函数失去幂等性,却换不来额外信号。

Consequences

  • 从 UI 已无法在操作者选定目录之外创建 Workspace;服务端控制的 --workspace-root 目标曾是约束新 workspace 文件夹落点的唯一手段,现在没有替代品。需要该约束的部署必须有意识地重新引入它。
  • 仅存的这条路径会浏览宿主机文件系统,因此选择器的可达范围现在是整台宿主机,而非一个配置好的父目录。这本就是浏览占用者的契约,本改动使它成为唯一的契约。
  • 挂载了 ui-workspace 但未挂任何 directory-picker 包的组合,已完全无法添加 Workspace;现在它通过不渲染按钮来说明这一点,而不是提供一个按名称创建的兜底。
  • 主视觉区的 chip 仍声明 aria-haspopup="menu",而直接拉起的路径实际弹出的是对话框。要让它如实,需要把流程的展示方式经 conversation.hero.workspace 的 owner 契约上报——决定权在流程,播报权在 chip,两者分属不同的包——因此这被列为一项具名的后续工作,而不是一处无声的不一致。本次新增的侧边栏按钮完全不作任何 popup 声明。