@dsh-electron/dsh-plugin-multi-root-workspace 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.en.md +44 -1
  2. package/CHANGELOG.md +44 -1
  3. package/README.en.md +155 -92
  4. package/README.md +156 -91
  5. package/cordis.patch.yml +57 -0
  6. package/lib/client.js +2678 -276
  7. package/lib/client.js.map +1 -1
  8. package/lib/command-CESuwlVt.js +1306 -0
  9. package/lib/command.js +2 -2
  10. package/lib/{compat-B6ZqbQBy.js → compat-DF_yL1bB.js} +160 -2
  11. package/lib/compat.js +1 -1
  12. package/lib/contract-DZ4zczDT.js +238 -0
  13. package/lib/{fs-D_Sw3o5z.js → fs-9JIzGaf3.js} +55 -19
  14. package/lib/fs.js +2 -1
  15. package/lib/index.js +10 -8
  16. package/lib/{instructions-DfzN-wVf.js → instructions-D19jRxN0.js} +3 -2
  17. package/lib/instructions.js +1 -1
  18. package/lib/lsp.js +7 -2
  19. package/lib/method-wrapper-CFY3p3j2.js +26 -0
  20. package/lib/process-fence.js +280 -0
  21. package/lib/providers.js +87 -0
  22. package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
  23. package/lib/registry.js +2 -2
  24. package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
  25. package/lib/sandbox-Kup4_ZIb.js +747 -0
  26. package/lib/sandbox.js +2 -1
  27. package/lib/scope.js +229 -20
  28. package/lib/types/client/RootSwitcher.d.ts +63 -0
  29. package/lib/types/client/WorkspaceFoldersAction.d.ts +39 -1
  30. package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
  31. package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
  32. package/lib/types/client/files-tree/adapter.d.ts +94 -0
  33. package/lib/types/client/files-tree/directory-node.d.ts +62 -0
  34. package/lib/types/client/files-tree/face.d.ts +107 -0
  35. package/lib/types/client/files-tree/locales.d.ts +10 -0
  36. package/lib/types/client/files-tree/primitives.d.ts +33 -0
  37. package/lib/types/client/files-tree/store.d.ts +81 -0
  38. package/lib/types/client/locales.d.ts +56 -10
  39. package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
  40. package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
  41. package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
  42. package/lib/types/client/open-in-app/adapter.d.ts +46 -0
  43. package/lib/types/client/open-in-app/applications.d.ts +4 -0
  44. package/lib/types/client/open-in-app/controller.d.ts +53 -0
  45. package/lib/types/client/open-in-app/locales.d.ts +10 -0
  46. package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
  47. package/lib/types/client/open-in-app/open-path.d.ts +13 -0
  48. package/lib/types/client/panel-client.d.ts +2 -1
  49. package/lib/types/client/styles.d.ts +1 -1
  50. package/lib/types/command.d.ts +16 -3
  51. package/lib/types/compat/client-icons.d.ts +18 -0
  52. package/lib/types/compat/dsh-version.d.ts +1 -1
  53. package/lib/types/compat/open-in-app-routes.d.ts +38 -0
  54. package/lib/types/compat/process-entrypoints.d.ts +31 -0
  55. package/lib/types/compat/process-result.d.ts +14 -0
  56. package/lib/types/compat/pty-marker.d.ts +83 -0
  57. package/lib/types/compat/sandbox-confine.d.ts +2 -0
  58. package/lib/types/compat/storage-zod.d.ts +65 -0
  59. package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
  60. package/lib/types/compat/windows-native.d.ts +68 -0
  61. package/lib/types/compat/windows-path-guard.d.ts +56 -0
  62. package/lib/types/compat/workspace-files.d.ts +2 -0
  63. package/lib/types/contract.d.ts +31 -19
  64. package/lib/types/dialects.d.ts +99 -3
  65. package/lib/types/fs.d.ts +32 -10
  66. package/lib/types/index.d.ts +7 -2
  67. package/lib/types/process-fence.d.ts +54 -0
  68. package/lib/types/profile-isolation.d.ts +178 -0
  69. package/lib/types/providers.d.ts +70 -0
  70. package/lib/types/registry.d.ts +132 -31
  71. package/lib/types/roots.d.ts +100 -13
  72. package/lib/types/sandbox.d.ts +59 -3
  73. package/lib/types/scope.d.ts +200 -14
  74. package/lib/types/windows-broker.d.ts +17 -0
  75. package/lib/types/windows-deny.d.ts +1 -0
  76. package/lib/types/windows-profile.d.ts +10 -13
  77. package/lib/types/windows-runner-contract.d.ts +97 -0
  78. package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
  79. package/lib/windows-runner.js +1256 -13
  80. package/lib/workspace-files.js +138 -3
  81. package/lib/workspace-routing-YPxaQ4V3.js +26 -0
  82. package/package.json +84 -62
  83. package/scripts/windows-broker/install.ps1 +89 -0
  84. package/scripts/windows-broker/rights.cs +58 -0
  85. package/scripts/windows-broker/service.cs +323 -0
  86. package/scripts/windows-broker/verify-install.ps1 +80 -0
  87. package/lib/command-CTdNvwt1.js +0 -791
  88. package/lib/method-wrapper-Zc-JlDP3.js +0 -51
  89. package/lib/sandbox-BRXl_FpF.js +0 -132
  90. package/lib/types/client/RootFileTree.d.ts +0 -12
  91. package/lib/types/panel-files.d.ts +0 -3
  92. package/lib/windows-profile-CiFsFYIV.js +0 -216
  93. package/lib/{registry-lease-win32-dy8TQMD9.js → registry-lease-win32-DTO6QpT9.js} +2 -2
package/README.md CHANGED
@@ -1,61 +1,60 @@
1
+ <div align="center">
2
+
1
3
  # dsh-plugin-multi-root-workspace
2
4
 
3
5
  中文 | [English](./README.en.md)
4
6
 
5
- ## 项目简介(What & Why)
6
-
7
- DSH(DeepSeek Harness)的外部插件 bundle:把 Workspace 的可写范围从"一个 canonical 目录"扩展为"**一个主根 + N 个附加根**",且**不修改上游仓库任何包**。
8
-
9
- 它解决的问题是:一个开发项目往往由多个独立 Git Repository 组成,而 DSH 原生只把会话原始项目当作唯一的可写根,Agent 跨仓库干活就得反复换会话。
10
-
11
- 这个插件的三个核心价值:
12
-
13
- - **Agent 零学习成本**:继续使用原生 `read` / `write` / `edit` / `bash` 工具,插件不新增任何 `workspace_*` 工具,只是把"哪些目录属于当前 Workspace"这一个问题的答案变多了。
14
- - **安全不降级**:多根授权走上游同款机制——进程内 fs fence + 内核级 runner(macOS Seatbelt / Linux bwrap / Landlock),fs 与 bash/PTY 共享同一条 scope;绝不退化为 danger-full-access 或提示词约束。
15
- - **不装就当不存在**:以 bundle patch 替换上游 `fs-sandbox` 与 `sandbox` 两行 provider;未配置附加根时行为与未装插件逐项一致,misconfiguration 一律响亮报错,从不静默降级。
7
+ </div>
16
8
 
17
- **已完成并发布:MVP `v0.1.0`(里程碑 M1–M3)**——组合与空根直通、多根能力与方言 grant、根注册表 / `/workspace-folders` 命令 / Workspace Folders 面板 / 跨仓库旅程 e2e。
9
+ ## 项目简介
18
10
 
19
- **已完成并发布:`v0.1.1` 硬化批次(H1–H4)**——跨进程 Registry Authority Lease(两个 DSH 进程共用 `$DSH_HOME` 时只有持锁者授予附加根,另一方 fail-closed 并在对方退出后接管)、面板主根改为 host session 推导(不再接受客户端指名的根)、DSH 兼容性从文档约定变成**启动时执行的代码契约**(精确 allowlist + 混装检测 + `src/compat/` 适配层,见 [CHANGELOG](./CHANGELOG.md) 的「升级注意」),以及**附加根自身的 `AGENTS.md` / `CLAUDE.md` 进入模型上下文**——原生指令发现从会话 cwd 向上走,永远到不了附加根。
11
+ DSH(DeepSeek Harness)原生只把会话启动的目录当作 Agent 唯一的可写根;本插件是一个不修改上游任何代码的外部扩展,把可写范围扩为"**一个主目录 + 多个附加目录**"。
20
12
 
21
- **已完成并发布:`v0.1.2`**——在 `v0.1.1` 契约不变的前提下,受支持的上游运行时扩展到 `0.1.6-alpha.2` 与 `0.1.7-alpha.1`(后者含 session format 4、进程内 `PluginPackages`、bash `execute().result()` 等适配);并修复 `0.1.6-alpha.2` 上 Workspace Folders 面板因 Session 目录删掉 `current` 而误显示「没有活动会话」的问题。详见 [CHANGELOG](./CHANGELOG.md)。
13
+ 一个开发项目往往由多个独立 Git 仓库组成,跨仓库干活原本得反复换会话。Codex、Cursor 等同类工具早已支持在一个工作区添加多个文件夹;本插件把同样的能力带给 DSH:把其他仓库登记为附加根,Agent 即可直接读写。
22
14
 
23
- **已完成并发布:`v0.1.3`**——在 `v0.1.2` 契约不变的前提下,受支持的上游运行时进一步扩展到 `0.1.7-alpha.2` 与 `0.1.7-rc.1`(形状与 `0.1.7-alpha.1` 相同,适配层无新分支;`rc.1` 起安装期按 `peerDependencies` 精确匹配宿主运行时)。详见 [CHANGELOG](./CHANGELOG.md)。
15
+ - **Agent 零学习成本**:继续使用原生 `read` / `write` / `edit` / `bash`,插件不新增任何工具,只是授权的目录变多了。
16
+ - **安全不降级**:附加根走上游同款授权机制——进程内文件围栏 + 内核级沙箱(macOS Seatbelt / Linux bwrap / Landlock / Windows ACL),文件与 bash 共享同一份授权范围,绝不退化成全开放或只靠提示词约束。
17
+ - **不配等于没装**:未登记附加根时行为与未装插件逐项一致;配置错误一律明确报错,从不静默降级。
24
18
 
25
- **已完成并发布:`v0.1.4`**——在 `v0.1.3` 契约不变的前提下,受支持的上游运行时扩展到 `0.1.7-rc.2`(形状与 `0.1.7-rc.1` 相同,适配层无新分支;cordis 仍是 `~4.0.4`)。详见 [CHANGELOG](./CHANGELOG.md)。
26
-
27
- **v0.1.5 已发布**。当前开发分支包含尚未发布的 DSH `0.2.1-alpha.1` / `0.2.1-alpha.2` 支持、动态 cwd / Session origin 分离,以及 LSP、Windows ACL、附加根文件浏览与 Worktree 能力;这些变更拟纳入 `v0.1.6`,发布状态见[路线账本](./docs/plans/active/2026-09-12-multi-root-workspace.md)。
28
-
29
- 进度、编号与发布状态的唯一真源是[路线图 §进度总账](./docs/plans/active/2026-09-12-multi-root-workspace.md#进度总账)(M1–M4 是 MVP 里程碑编号,H1–H4 是 `v0.1.1` 批次编号,其中 H1 即 M4);逐项证据见各[已完成计划](./docs/plans/README.md),每个版本的用户可见变更见 [CHANGELOG](./CHANGELOG.md)。
19
+ 当前发布版本 `v0.1.7`。各版本的用户可见变更见 [CHANGELOG](./CHANGELOG.md);进度与发布状态见[路线图 §进度总账](./docs/plans/active/2026-09-12-multi-root-workspace.md#进度总账)。
30
20
 
31
21
  ## 快速开始
32
22
 
33
- 已有 DSH 运行时(web / Electron 桌面 / headless 均可)时,最短路径是两步——插件已发布到 [npm](https://www.npmjs.com/package/@dsh-electron/dsh-plugin-multi-root-workspace):
23
+ 插件已发布到 [npm](https://www.npmjs.com/package/@dsh-electron/dsh-plugin-multi-root-workspace)。可以在 DSH 的插件安装页面填入包名 `@dsh-electron/dsh-plugin-multi-root-workspace`,也可以用命令行两步装完:
34
24
 
35
25
  ```sh
36
26
  dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
37
27
  dsh --profile web
28
+ # --profile 按你的启动方式选择,可选值见 DSH 文档
38
29
  ```
39
30
 
40
- 其他安装来源(GitHub 仓库 / 本地源码)与源码方式运行 DSH 的命令差异,见[安装](#安装)。
31
+ tarball、GitHub、本地源码等其他安装方式见[安装](#安装)。
41
32
 
42
33
  启动后侧栏底部出现 **Folders**(`🗂`)动作,或直接在会话里:
43
34
 
44
35
  ```text
45
- /workspace-folders add ~/code/another-repo
36
+ /workspace-folders add ~/code/another-repo [--inherit|--read-only|--read-write]
46
37
  ```
47
38
 
48
- 之后 Agent 即可在该目录读写、跑 bash——与本会话的主根同权。DSH 的动态 working directory 只影响新操作的相对路径和执行目录,切换 cwd 不会新增授权,也不会改变注册表归属。
39
+ 之后 Agent 即可在该目录读写、跑 bash——默认 `inherit`,访问级别跟随会话模式。DSH 的动态 working directory 只影响新操作的相对路径和执行目录,切换 cwd 不会新增授权,也不会改变注册表归属。
49
40
 
50
41
  ## 环境要求
51
42
 
52
- - **使用本源码构建的插件**:需要一个受支持的 DSH 运行时 —— 当前是 **`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1` 与 `0.2.1-alpha.2`**,别的版本装不上也不会跑(见下)。`dsh plugin` 会把包装进对应 profile,无需本地 Node 工具链
53
- - **从源码构建 / 参与**:**Node.js** `^22.19.0 || >=24`(仓库 `engines` 钉住)、**Git**、**pnpm 11**(`packageManager` 钉 `pnpm@11.25.0`,建议经 corepack 启用)
54
- - **DSH 运行时**:开发依赖精确 pin 在 `0.2.1-alpha.2`(受支持版本里的基线);升级流程见[开发工作流](./docs/development/plugin-development-workflow.md)
55
- - **平台支持**:macOS Seatbelt / Linux bwrap、Landlock;当前开发分支还实现 Windows root-set ACL runner,平台验证状态见[路线账本](./docs/plans/active/2026-09-12-multi-root-workspace.md#b1b3第二期-p12026-10-02)。
56
- - 运行冒烟测试**不需要模型凭据**:e2e 的模型轮次由内联的脚本化模型端点提供
43
+ **使用本插件**:一个下表中的 DSH 版本(精确匹配,不是范围)。`dsh plugin` 负责安装,无需本地 Node 工具链。
44
+
45
+ | DSH 系列 | 受支持的版本 |
46
+ | --- | --- |
47
+ | `0.1.5` | `0.1.5-rc.2` |
48
+ | `0.1.6` | `0.1.6-alpha.1`、`0.1.6-alpha.2` |
49
+ | `0.1.7` | `0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2` |
50
+ | `0.2.0` | `0.2.0-rc.1`、`0.2.0-rc.2` |
51
+ | `0.2.1` | `0.2.1-alpha.1`、`0.2.1-alpha.2`、`0.2.1-alpha.3`、`0.2.1-alpha.4`、`0.2.1-alpha.5`、`0.2.1-rc.1`、`0.2.1-rc.2` |
52
+
53
+ DSH 版本不在表内、或 `@deepseek-ai/dsh-*` 各包版本不一致时,插件不会启动,行为等同没装。
54
+
55
+ **从源码构建 / 贡献**:Node.js `^22.19.0 || >=24`,pnpm `11.25.0`(建议经 corepack 启用);开发 pin 的 DSH 为 `0.2.1-rc.2`。
57
56
 
58
- **受支持的 DSH 版本是一份精确清单,不是一个范围。** 因为本插件替换的是 `ctx.fs` 与 `ctx.sandbox`——安全边界本身——而它识别内核沙箱方言靠的是对具体上游版本实测出来的 argv 形状。所以 `peerDependencies` 只列真正跑过全套验证的版本,启动时也会再查一遍:宿主的版本不在清单上,或者若干个 `@deepseek-ai/dsh-*` 混装了不同版本,那么 fs / sandbox / registry / instructions / lsp / workspace-files 六行**不启动**,组合退化成"没装这个插件",并打印一条说明。诊断办法见[故障排查:DSH 版本不在支持矩阵上](./docs/troubleshooting/unsupported-dsh-release.md),理由见 [ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md)。
57
+ **运行平台**:macOS(Seatbelt)、Linux(bwrap / Landlock)、Windows(root-set ACL runner)。
59
58
 
60
59
  ## 安装
61
60
 
@@ -63,19 +62,19 @@ dsh --profile web
63
62
 
64
63
  | 来源 | 命令 | 说明 |
65
64
  | --- | --- | --- |
66
- | npm 注册表(推荐) | `dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace` | 预构建产物,即装即用;首次 `add` 需要回答一次 `allowBuilds`(见下) |
67
- | tarball | `dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-<version>.tgz` | 预构建离线包,适合内网/离线;同样需要那一次 `allowBuilds` 回答 |
68
- | 本地路径 | `dsh plugin --profile web add /path/to/package/dsh-plugin-multi-root-workspace` | pnpm `link:` 链接本地 checkout,依赖已在本仓库装好,适合开发调试 |
69
- | GitHub / git | `dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace` | 拉源码由 `prepare` 现场构建;需要两次 `allowBuilds` 回答(本包 + `koffi`),建议锁定 tag |
65
+ | npm 注册表(推荐) | `dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace` | 预构建产物,即装即用 |
66
+ | GitHub | `dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace` | 安装时自动从源码构建;`allowBuilds` 需含本包,建议锁定版本 |
67
+ | tarball | `dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-<version>.tgz` | 预构建离线包,适合内网/离线 |
68
+ | 从源码安装 | `dsh plugin --profile web add /path/to/package/dsh-plugin-multi-root-workspace` | pnpm `link:` 链接本地 checkout,适合开发调试;无需 `allowBuilds` |
70
69
 
71
- **首次安装需要回答一次 `allowBuilds`,四种来源皆然。** 本插件带一个需要构建的原生依赖 `koffi`(Windows 上 Registry Authority 用的 FFI 封装;其他平台装了不用),而 pnpm ≥10 默认不运行任何依赖的生命周期脚本。所以第一次 `dsh plugin add` 会以 `[ERR_PNPM_IGNORED_BUILDS]` 失败,并把待决项写进该 profile 的 `pnpm-workspace.yaml`:
70
+ **首次安装时 `dsh plugin add` 会以 `[ERR_PNPM_IGNORED_BUILDS]` 失败**:在该 profile 的 `pnpm-workspace.yaml` 里补上下面这行,再执行一次 `add` 即可:
72
71
 
73
72
  ```yaml
74
73
  allowBuilds:
75
- koffi: true # dsh 留的占位是 "set this to true or false"
74
+ koffi: true
76
75
  ```
77
76
 
78
- 把它改成 `true` 再执行一次 `add` 即可。把这一行理解为**允许该依赖的安装脚本在你的机器上执行**(不在 agent 运行的任何沙箱之内)——`koffi` 的脚本只做本地预编译。诊断步骤见[故障排查:安装停在构建授权](./docs/troubleshooting/install-stops-at-build-approval.md)。
77
+ 如果重跑退出 0 但插件没有进入 `dsh.profile.bundles`,做一次 `remove` + `add`。更多排查见[故障排查](./docs/troubleshooting/install-stops-at-build-approval.md)。
79
78
 
80
79
  ### 从 npm 安装(推荐)
81
80
 
@@ -83,18 +82,18 @@ allowBuilds:
83
82
  dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
84
83
  ```
85
84
 
86
- 安装的是预构建产物(不需要编译本插件本身),但首次仍要回答上面那一次 `allowBuilds`。
85
+ 安装的是预构建产物,首次安装需要添加 `allowBuilds`。
87
86
 
88
87
  ### 从 tarball 安装
89
88
 
90
89
  ```sh
91
90
  pnpm pack @dsh-electron/dsh-plugin-multi-root-workspace
92
91
  # 或从 GitHub Release 资产下载,例如:
93
- # https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/download/v0.1.5/dsh-electron-dsh-plugin-multi-root-workspace-0.1.5.tgz
94
- dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-0.1.5.tgz
92
+ # https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/download/v0.1.7/dsh-electron-dsh-plugin-multi-root-workspace-0.1.7.tgz
93
+ dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-0.1.7.tgz
95
94
  ```
96
95
 
97
- 同样是预构建产物(不需要编译本插件本身),适合内网或离线环境交付;首次 `add` 同样要回答那一次 `allowBuilds`。
96
+ 同样是预构建产物,适合内网或离线环境交付;首次安装同样需要添加 `allowBuilds`。
98
97
 
99
98
  ### 从 GitHub 安装
100
99
 
@@ -102,7 +101,7 @@ dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-0.1.
102
101
  dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace
103
102
  ```
104
103
 
105
- pnpm ≥10 下首次 `add` 会失败:git 安装拉取的是**源码而非构建产物**,包内自包含的 `prepare` 脚本要现场构建(直接转译 `src/`,不做类型检查)。按 `dsh` 的提示把 pnpm 打印的包键写入该 profile 的 `pnpm-workspace.yaml`——这条来源要回答**两个**待决项:
104
+ GitHub 来源装的是源码,`add` 时会自动构建,不需要手动编译;但 `allowBuilds` 要写两行——本包自身和 `koffi`:
106
105
 
107
106
  ```yaml
108
107
  allowBuilds:
@@ -110,93 +109,155 @@ allowBuilds:
110
109
  koffi: true
111
110
  ```
112
111
 
113
- 然后重新执行 `add` 即可。建议锁定 tag(如 `#v0.1.5`),让后续推送无法悄悄改变实际运行的内容:
112
+ 建议锁定版本(如 `#v0.1.7`),让后续推送无法悄悄改变实际运行的内容:
114
113
 
115
114
  ```sh
116
- dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace#v0.1.5
115
+ dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace#v0.1.7
117
116
  ```
118
117
 
119
- ### 从本地源码安装(开发调试)
118
+ ### 从本地源码安装
120
119
 
121
120
  ```sh
122
121
  git clone https://github.com/cherrchen/dsh-plugin-multi-root-workspace.git
123
122
  cd dsh-plugin-multi-root-workspace
124
- export CI=true # 无 TTY 时 pnpm 的依赖自检会中止,见开发工作流 §8
123
+ export CI=true
125
124
  pnpm install
126
- pnpm build # 生成 lib/(未纳入 Git),制品测试与安装都依赖它
127
- dsh plugin --profile web add "$PWD"
125
+ pnpm build
126
+ dsh plugin --profile web add /path/to/repo
128
127
  ```
129
128
 
130
- > **提示**:如果你的 DSH 是 clone 源码方式使用(而非 `npm install -g @deepseek-ai/deepseek-harness`),`dsh` 不在全局 PATH 里,请把上述命令中的 `dsh` 换成 `pnpm dsh`——例如 `pnpm dsh plugin --profile web add ...`、`pnpm dsh --profile web`。
129
+ > [!TIP]
130
+ > `dsh` 不在 PATH 里时(例如用源码仓库跑 DSH),把命令里的 `dsh` 换成 `pnpm dsh`。
131
131
 
132
132
  ## 运行方法
133
133
 
134
- 开发自检(每步的预期结果):
134
+ 开发自检:
135
135
 
136
136
  ```sh
137
- pnpm lint && pnpm typecheck # 预期:0 警告 0 错误;两个 tsconfig 全部通过
138
- pnpm test # 预期:全部通过;本机没有的内核 runner 用例会显式 skip 并打印原因
139
- pnpm kernel:probe # 预期:报告本机可用的内核 runner(seatbelt / bwrap / landlock)
140
- pnpm smoke # 预期:compose 40/40、behavior 99/99、journey 55/55
141
- pnpm docs:check # 预期:0 errors, 0 warnings
137
+ pnpm lint && pnpm typecheck
138
+ pnpm test
139
+ pnpm kernel:probe
140
+ pnpm smoke
141
+ pnpm docs:check
142
142
  ```
143
143
 
144
144
  安装进 DSH 运行时并验证组合:
145
145
 
146
146
  ```sh
147
- dsh plugin --profile web add "$PWD"
148
- dsh --profile web --dump-config
147
+ dsh plugin --profile web add /path/to/repo
148
+ dsh --profile web --dump-config | grep -A3 'multi-root'
149
+ ```
150
+
151
+ 预期输出:
152
+
153
+ ```text
154
+ # == @deepseek-ai/dsh-base, patched by @dsh-electron/dsh-plugin-multi-root-workspace
155
+ - id: sandbox
156
+ name: '@deepseek-ai/dsh-sandbox-local'
157
+ disabled: true
158
+ --
159
+ # == @deepseek-ai/dsh-base, patched by @dsh-electron/dsh-plugin-multi-root-workspace
160
+ - id: fs-sandbox
161
+ name: '@deepseek-ai/dsh-fs-sandbox'
162
+ disabled: true
163
+ --
164
+ # == @dsh-electron/dsh-plugin-multi-root-workspace
165
+ - id: multi-root-compat
166
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/compat'
167
+ - id: multi-root-fs
168
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/fs'
169
+ - id: multi-root-sandbox
170
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/sandbox'
171
+ - id: multi-root-providers
172
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/providers'
173
+ - id: multi-root-scope
174
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/scope'
175
+ config:
176
+ toolAccess: {}
177
+ - id: multi-root-registry
178
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/registry'
179
+ config:
180
+ leasePath: !!js dshHomePath('storages/multi_root_workspace.lock')
181
+ maxAdditionalRoots: 16
182
+ - id: multi-root-instructions
183
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/instructions'
184
+ config:
185
+ maxBytes: 65536
186
+ - id: multi-root-lsp
187
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/lsp'
188
+ - id: multi-root-workspace-files
189
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/workspace-files'
190
+ - id: multi-root-command
191
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/command'
192
+ - id: multi-root-client
193
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace'
149
194
  ```
150
195
 
151
- 预期:组合里**仅** `fs-sandbox` 与 `sandbox` 两行被替换为插件的 `multi-root-fs` / `multi-root-sandbox`,并插入 10 行(`multi-root-compat` / `fs` / `sandbox` / `scope` / `registry` / `instructions` / `lsp` / `workspace-files` / `command` 与 client 载体行 `multi-root-client`);`bash-sandbox` 保持上游(bash 与 PTY 经 `ctx.sandbox` 取根)。启动 `dsh --profile web` 后,侧栏底部出现 Folders 动作。
196
+ 即上游 `fs-sandbox` 与 `sandbox` 两行被禁用、由插件同名服务接管,插件自身的 `multi-root-*` 行全部挂载。启动后侧栏底部会出现 **Folders** 动作。
152
197
 
153
198
  ## 使用方法
154
199
 
155
- **命令**(文本入口,headless 下同样可用):
200
+ ### 命令
156
201
 
157
202
  ```text
158
- /workspace-folders # 列出主根与附加根
159
- /workspace-folders add <绝对路径> # 无参数时打开系统目录选择器
160
- /workspace-folders alias 1 支付 # 给第 1 个附加根起别名
161
- /workspace-folders remove 1 # 按序号或路径移除
162
- /workspace-folders reveal 1 # 在文件管理器中显示
203
+ /workspace-folders [list] # 列出主根与附加根(含每根访问级别与状态)
204
+ /workspace-folders add <绝对路径> [--inherit|--read-only|--read-write] # 不带路径时打开系统目录选择器;新登记默认 inherit
205
+ /workspace-folders access <序号|路径> inherit|read-only|read-write # 切换访问级别(扩权要求该根此刻可用)
206
+ /workspace-folders alias <序号|路径> [别名] # 不带别名则清除已有别名
207
+ /workspace-folders remove <序号|路径> # 序号即 list 输出的编号
208
+ /workspace-folders reveal <序号|路径> # 在文件管理器中显示
209
+ /workspace-folders isolate-profiles [--apply] # 检查 $DSH_HOME profile 依赖导入方式与多链接文件;--apply 写入 copy 配置
210
+ /workspace-folders help # 查看命令帮助
163
211
  ```
164
212
 
165
- `add` 后执行 `list` 的预期输出:
213
+ 路径可带引号;与已有根重复、嵌套在已有根内部、或就是主根本身的路径会被拒绝。
214
+
215
+ 访问级别是三态的([ADR-0017](./docs/decisions/ADR-0017-three-state-root-access-and-sandbox-modes.md),语义细节见[使用指南](./docs/guide/README.md#访问级别数量上限与-per-tool-封顶)):`inherit` 跟随会话模式——`workspace-write` 可写、`read-only` 拒写、`danger-full-access` 不受限;`read-only` 在任何模式下都拒写(包括 Full-access);`read-write` 固定在只读会话里仍可写。旧版插件登记的可写记录升级后按 `inherit` 读。每个主根最多登记 16 个附加根(`maxAdditionalRoots` 配置可调至 64)。宿主还可以在 `multi-root-scope` 行配置 `toolAccess`,例如 `{ bash: 'read-only' }` 让 bash 永远不能写任何附加根(仍可写主根)。
216
+
217
+ `add` 后执行 `list` 的预期输出(`workspace-write` 会话,website 登记为只读):
166
218
 
167
219
  ```text
168
220
  Workspace root (primary; access follows the current sandbox mode): /home/me/monorepo
169
- 1 /home/me/payments-service [支付]
170
- 2 /home/me/website
171
- Writable additional roots: 2 of 2.
221
+ 1 /home/me/payments-service [支付] (inherit)
222
+ 2 /home/me/website (read-only)
223
+ 2 of 16 additional roots.
224
+ Writable additional roots under the current mode: 1 of 2.
172
225
  ```
173
226
 
174
- **面板**(Web GUI):侧栏底部的 **Folders** 对话框列出主根与附加根,支持添加(走组合好的目录选择器或手输路径)、移除、别名、复制路径、在文件管理器中显示与上下移排序;文案中英双语跟随界面语言。根目录的状态会如实显示:`missing`(暂时不存在)、`redirected`(被替换成指向别处的符号链接)的根**保留登记但暂不授予**,目录恢复后 `list` 或面板刷新即自动重新授予,无需重启。
227
+ ### 面板
228
+
229
+ 侧栏底部的 **Folders** 对话框列出主根与附加根,支持添加、移除、别名、切换只读/可写、复制路径、在文件管理器中显示与上下移排序。
230
+
231
+ ### 模型侧
175
232
 
176
- **模型侧**:附加根拓扑经 `systemPrompt.context` 快照随每次请求告知模型(同 workspace、cwd 不变),无需新增内省工具。授权规则(canonical 化、`recordedPath` 防符号链接转移、冲突拒绝)的完整语义见[架构文档](./docs/architecture/multi-root-workspace.md)。
233
+ 附加根的拓扑信息会注入系统提示词,让模型知道当前有哪些目录可用,授权变化时信息随之更新,不需要向模型提供新的工具。
177
234
 
178
235
  ## 项目结构
179
236
 
180
237
  ```text
181
- src/
182
- roots.ts 纯规则层:canonical 化、冲突校验、登记状态分类(available/missing/redirected/invalid)
183
- registry.ts 根注册表:dsh-storage-domain 持久化,同一主根的变更在一条队列里串行
184
- registry-lease.ts / registry-lease-win32.ts
185
- 跨进程 Registry Authority:POSIX flock 与 Windows named semaphore(进程死亡由内核释放)
186
- scope.ts ctx.multiRootScope:唯一的授权源,兼发模型可见的拓扑快照
187
- fs.ts 多根文件系统 provider(进程内 fence,子类自上游 LocalFileSystem)
188
- sandbox.ts 多根内核沙箱 provider(子类自上游 LocalSandboxProvider)
189
- dialects.ts Seatbelt / bwrap / Landlock profile 的识别与附加 grant 拼装(不认识即响亮报错)
190
- containment.ts 路径包含判定(词法快速路径 + dev/ino 别名回退)
191
- instructions.ts 附加根 AGENTS.md / CLAUDE.md 的发现、增量投递、预算与撤回(顶层 + 本会话触碰过的子目录,经 agent/pre-step 注入)
192
- compat.ts multi-root-compat 启动门禁(版本 allowlist 与混装判定)
193
- compat/ 版本差异适配层:dsh-version / sandbox-confine / agent-instructions / llm-message(可选 peer 按需加载)
194
- command.ts /workspace-folders 命令与面板 RPC 的 host 半部
195
- contract.ts 面板线协议(zod 双端校验,可内联进浏览器 bundle)
196
- client/ 浏览器半部:侧栏动作、对话框、双语词典
197
- tests/ 差分 parity、方言真实执行矩阵、契约往返、组件与 locale 门禁、跨进程 lease e2e、指令注入
198
- scripts/ 冒烟(compose / behavior / journey)、文档与内核 runner 检查、兼容性契约检查、升级流程
199
- docs/ 需求、架构、决策记录(ADR)、计划、开发工作流
238
+ .
239
+ ├── src/
240
+ │ ├── roots.ts 纯规则层:canonical 化、冲突校验、登记状态分类(available/missing/redirected/invalid)
241
+ │ ├── registry.ts 根注册表:dsh-storage-domain 持久化,同一主根的变更在一条队列里串行
242
+ │ ├── registry-lease.ts 跨进程 Registry Authority:POSIX flock(进程死亡由内核释放)
243
+ │ ├── registry-lease-win32.ts 同上,Windows named semaphore 实现
244
+ │ ├── scope.ts ctx.multiRootScope:唯一的授权源,兼发模型可见的拓扑快照
245
+ │ ├── fs.ts 多根文件系统 provider(进程内 fence,子类自上游 LocalFileSystem)
246
+ │ ├── sandbox.ts 多根内核沙箱 provider(子类自上游 LocalSandboxProvider)
247
+ │ ├── dialects.ts Seatbelt / bwrap / Landlock profile 的识别与附加 grant 拼装(不认识即响亮报错)
248
+ │ ├── windows-*.ts Windows root-set ACL runner 与 restricted-token 执行
249
+ │ ├── containment.ts 路径包含判定(词法快速路径 + dev/ino 别名回退)
250
+ │ ├── instructions.ts 附加根 AGENTS.md / CLAUDE.md 的发现、增量投递、预算与撤回(顶层 + 本会话触碰过的子目录,经 agent/pre-step 注入)
251
+ │ ├── lsp.ts 附加根按所在根初始化独立 LSP workspace
252
+ │ ├── workspace-files.ts Files 页签附加根文件浏览的 host 路由与 confinement
253
+ │ ├── compat.ts multi-root-compat 启动门禁(版本 allowlist 与混装判定)
254
+ │ ├── compat/ 版本差异适配层:dsh-version / sandbox-confine / agent-instructions / llm-message 等(可选 peer 按需加载)
255
+ │ ├── command.ts /workspace-folders 命令与面板 RPC 的 host 半部
256
+ │ ├── contract.ts 面板线协议(zod 双端校验,可内联进浏览器 bundle)
257
+ │ └── client/ 浏览器半部:侧栏动作、Folders 对话框、Files 页签根切换器、双语词典
258
+ ├── tests/ 差分 parity、方言真实执行矩阵、契约往返、组件与 locale 门禁、跨进程 lease e2e、指令注入
259
+ ├── scripts/ 冒烟(compose / behavior / journey)、文档与内核 runner 检查、兼容性契约检查、升级流程
260
+ └── docs/ 需求、架构、决策记录(ADR)、计划、开发工作流
200
261
  ```
201
262
 
202
263
  ## 贡献指南
@@ -215,8 +276,11 @@ docs/ 需求、架构、决策记录(ADR)、计划、开发工
215
276
  - 跨进程单写者:同一 `$DSH_HOME` 上同时只允许一个 DSH 进程持有根登记表;另一个进程显示登记表不可用(`registry-contended`),持锁者退出或崩溃后刷新即接管——这是刻意的 fail-closed,不是待修的竞态([ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md))。
216
277
  - 支持矩阵是精确版本 allowlist:宿主版本不在清单上、或核心包混装了不同版本时,`fs` / `sandbox` / `registry` / `instructions` / `lsp` / `workspace-files` 六行**不启动**,组合退化为"没装这个插件"([ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md))。
217
278
  - Windows 多根 runner 继承上游 partial enforcement、caller-owned 目录及 private temp 分离条件;已启动进程保留启动时根集合,后续进程使用更新后的集合([ADR-0011](./docs/decisions/ADR-0011-multi-root-workspace-consumers.md))。
218
- - 附加根与主根同权(无 per-root read-only);附加根不能作为 bash/PTY 的默认工作目录(session cwd 语义不变)。
219
- - 附加根文件树位于“工作区目录”对话框,采用手动刷新;可展开目录和预览前 200 行文本。主根原生 Files tab 保留,有 actions slot 时提供打开该对话框的入口。LSP 按附加文件所在根初始化独立 workspace;需要宿主配置对应语言服务器([ADR-0011](./docs/decisions/ADR-0011-multi-root-workspace-consumers.md))。
279
+ - **Windows 上的网络与设备路径不能成为附加根**:UNC(`\\server\share`)、`\\?\`、`\\.\` 与映射网络盘在登记时即以 `unsupported-path` 拒绝——restricted token 的 capability SID 无法绑定 SMB 访问([ADR-0011 修订](./docs/decisions/ADR-0011-multi-root-workspace-consumers.md))。
280
+ - **Windows 受限 shell 是 pwsh,不含 Git Bash**:MSYS 在 restricted Low-integrity token 下无法启动(上游 runner 与插件 runner 表现一致,属上游限制);上游 runner 的 confined 子进程因继承 ignore-Ctrl+C 标志而收不到 Ctrl+C,插件 runner 下子进程能收到并由 runner 镜像其退出码。
281
+ - 附加根访问级别是三态的:新登记默认 `inherit`(跟随会话模式),`--read-only` 在任何模式下都拒写(包括 Full-access),`--read-write` 固定在只读会话里仍可写(写授权问题,不是读 confinement;[ADR-0017](./docs/decisions/ADR-0017-three-state-root-access-and-sandbox-modes.md))。已启动的进程与持久终端保留启动时的 deny 集——一个在某根变为只读之前就启动的终端保持不受限,直到重启;cap/access 变化只约束之后的调用与新进程([ADR-0013](./docs/decisions/ADR-0013-per-root-and-per-tool-access.md));提供 workingDirectory 的宿主可以把附加根作为后续工具的执行目录,Session 原始项目与登记身份不变([ADR-0012](./docs/decisions/ADR-0012-session-origin-and-effective-cwd.md))。
282
+ - **上游选择 Landlock 的 Linux 主机上,受限 Full-access 需要 bwrap**:Landlock 的 allowlist 表达不了「全部可写、除这些根」,该组合改选 bubblewrap 拼出拒写边界;主机没有可用的 bwrap(未安装或禁用非特权 userns)时,存在任何拒写边界(只读根或受保护的 `$DSH_HOME`)的 Full-access 命令在启动前失败关闭。启用 bwrap,或在该主机上避免在 Full-access 下使用显式只读根([ADR-0017](./docs/decisions/ADR-0017-three-state-root-access-and-sandbox-modes.md))。
283
+ - 附加根文件树由 **Files 页签的根选择器**承载:头部入口列出主根与全部附加根,旁边仍显示选中根的完整路径;选中附加根时逐字节复刻的上游 Files 树体接管树区(懒加载目录、原生行式与图标、watch 自动刷新、文件点击进宿主 viewer、header 原位提供手动刷新),header 右侧「在应用中打开」按钮同时由逐字节复刻的上游控件顶替、对选中根而非主根发起打开;切回主根即恢复原生视图;对话框回归纯管理。该接管依赖上游 `data-*` DOM 钩子,钩子失效时静默退化为原生观感([ADR-0015](./docs/decisions/ADR-0015-files-tab-plugin-owned-browsing.md)、[ADR-0016](./docs/decisions/ADR-0016-files-tab-native-tree-fork.md))。LSP 按附加文件所在根初始化独立 workspace;需要宿主配置对应语言服务器([ADR-0011](./docs/decisions/ADR-0011-multi-root-workspace-consumers.md))。
220
284
  - 命令的输出文案为英文(host 侧没有活动语言信息),面板文案中英双语跟随界面语言。
221
285
  - Workspace Folders 面板是「侧栏底部动作 + 对话框」,不是独立全屏面板:`sidebar.footer.action` 是所有受支持版本都提供的插槽,而 `sidebar.panellist`/`main` 不是。
222
286
 
@@ -225,6 +289,7 @@ docs/ 需求、架构、决策记录(ADR)、计划、开发工
225
289
  项目长期文档位于 [`docs/`](./docs/README.md):
226
290
 
227
291
  - [版本变更记录(CHANGELOG)](./CHANGELOG.md)
292
+ - [用户指南](./docs/guide/README.md)
228
293
  - [需求](./docs/requirements/multi-root-workspace.md)
229
294
  - [目标架构](./docs/architecture/multi-root-workspace.md)
230
295
  - [上游调研](./docs/reference/multi-root-workspace-research.md)
package/cordis.patch.yml CHANGED
@@ -42,14 +42,43 @@
42
42
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/fs'
43
43
  - id: multi-root-sandbox
44
44
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/sandbox'
45
+ # The provider-conflict gate: `ctx.fs` and `ctx.sandbox` must be THIS
46
+ # bundle's branded providers — verified by a well-known symbol, not
47
+ # instanceof, so duplicated installs still count. If another plugin's
48
+ # provider took either key (or took it over later — cordis restarts an
49
+ # injecting fiber when an injected service changes, so this row re-checks),
50
+ # the row logs which service is foreign and throws. Every widened-authority
51
+ # row below injects `multiRootProviders`, so a conflicted composition keeps
52
+ # the registry unmounted, the scope empty, and nothing granted, regardless
53
+ # of patch order — fail loud, never a widened grant on an unverified
54
+ # provider.
55
+ - id: multi-root-providers
56
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/providers'
45
57
  - id: multi-root-scope
46
58
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/scope'
59
+ config:
60
+ # Per-tool write caps: a tool listed here may never write ANY
61
+ # additional root (the primary root is unaffected). `read-only` is the
62
+ # only legal value; anything else fails closed — every additional-root
63
+ # write is refused for the session. Empty map: no cap at all.
64
+ toolAccess: {}
65
+ # The harness home holds the store, the lease file, and the user's
66
+ # profiles — nothing an ambient Full-access process may touch. While
67
+ # ANY deny boundary exists under danger-full-access (a restricted root
68
+ # or an unavailable registry authority), the scope appends this path
69
+ # to the deny set, so fs writes and spawned processes refuse it.
70
+ dshHome: !!js dshHomePath()
47
71
  - id: multi-root-registry
48
72
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/registry'
49
73
  config:
50
74
  # Store-wide kernel lease beside the JSON domain document. A custom
51
75
  # storage-json.root must override this path in lockstep (ADR-0007).
52
76
  leasePath: !!js dshHomePath('storages/multi_root_workspace.lock')
77
+ # Cap on stored additional-root records per primary root (integer,
78
+ # 1..64; the count covers every state, so it is stable). A store that
79
+ # already exceeds it keeps granting — only new adds are refused. Bench
80
+ # evidence for the default: docs/reference/bench-root-scale-*.json.
81
+ maxAdditionalRoots: 16
53
82
  # Upstream `agent-instructions` only discovers instruction files from the
54
83
  # session cwd upward, so an additional root's own AGENTS.md is invisible to
55
84
  # the model. This row closes that gap for the additional roots only; the
@@ -63,7 +92,35 @@
63
92
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/lsp'
64
93
  - id: multi-root-workspace-files
65
94
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/workspace-files'
95
+ # The B9 consumer seam for `danger-full-access`: the shell executor, the
96
+ # PTC runtime, and the PTY backend all skip `ctx.sandbox.confine` in that
97
+ # mode, so an explicit `read-only` additional root would silently stay
98
+ # writable. This row wraps `ctx.subprocess.spawn` / `spawnTerminal`,
99
+ # `ctx.shell.execute` (and `run` / `start` where a release still exposes
100
+ # them), and `ctx.ptcRuntime.run` at their public methods — under
101
+ # Full-access with restricted roots it asks `ctx.sandbox` for a restricted
102
+ # profile prefix and runs the call inside it, and while the registry
103
+ # authority is unavailable (contended, storage-failed, config-invalid) it
104
+ # limits the same calls to primary-root-only writes (no shared temp) instead of
105
+ # trusting the empty deny set. `spawnTerminal` only confines agent PTYs:
106
+ # every supported `dsh-terminal-bash` backend stamps `DSH_PTY_SESSION_ID`
107
+ # into the spawn env; a spec without it — the panel's terminal controller
108
+ # calls `spawnTerminal` directly — is a USER terminal and passes through
109
+ # in every mode (`ctx.terminals` is `isolate`d on the web profile, so the
110
+ # registry entry itself is not a reachable seam). The marker is a load
111
+ # contract, not an assumption: `multi-root-compat` probes the installed
112
+ # `dsh-terminal-bash` build for `DSH_PTY_SESSION_ID` at startup and refuses
113
+ # to load when an installed backend no longer stamps it. A dialect that
114
+ # cannot express "ambient minus subtree" (Landlock, Windows ACL) fails
115
+ # closed before the process starts.
116
+ - id: multi-root-process-fence
117
+ name: '@dsh-electron/dsh-plugin-multi-root-workspace/process-fence'
66
118
  - id: multi-root-command
67
119
  name: '@dsh-electron/dsh-plugin-multi-root-workspace/command'
120
+ config:
121
+ # `/workspace-folders isolate-profiles` inspects the same canonical
122
+ # home the scope protects; spelled exactly like the scope row so the
123
+ # two can never disagree about which home was checked.
124
+ dshHome: !!js dshHomePath()
68
125
  - id: multi-root-client
69
126
  name: '@dsh-electron/dsh-plugin-multi-root-workspace'