@dsh-electron/dsh-plugin-multi-root-workspace 0.1.0 → 0.1.1
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.
- package/CHANGELOG.en.md +77 -0
- package/CHANGELOG.md +77 -0
- package/README.en.md +94 -20
- package/README.md +95 -19
- package/cordis.patch.yml +22 -1
- package/lib/client.js +102 -58
- package/lib/client.js.map +1 -1
- package/lib/{command-BSkrYOAf.js → command-DyOWujPo.js} +24 -61
- package/lib/command.js +2 -2
- package/lib/compat-nFYqN5wt.js +267 -0
- package/lib/compat.js +2 -0
- package/lib/{fs-D7UoCb7T.js → fs-D_Sw3o5z.js} +7 -3
- package/lib/fs.js +1 -1
- package/lib/index.js +8 -6
- package/lib/instructions-BRGoC82G.js +635 -0
- package/lib/instructions.js +2 -0
- package/lib/registry-DySMbjgW.js +793 -0
- package/lib/registry-lease-win32-dy8TQMD9.js +93 -0
- package/lib/registry.js +2 -466
- package/lib/{sandbox-BhuMTDgH.js → sandbox-D8Fw95FM.js} +51 -4
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +1 -1
- package/lib/types/client/locales.d.ts +6 -0
- package/lib/types/command.d.ts +17 -0
- package/lib/types/compat/agent-instructions.d.ts +138 -0
- package/lib/types/compat/dsh-version.d.ts +119 -0
- package/lib/types/compat/llm-message.d.ts +34 -0
- package/lib/types/compat/sandbox-confine.d.ts +58 -0
- package/lib/types/compat.d.ts +85 -0
- package/lib/types/contract.d.ts +6 -8
- package/lib/types/index.d.ts +19 -5
- package/lib/types/instructions.d.ts +124 -0
- package/lib/types/registry-lease-win32.d.ts +29 -0
- package/lib/types/registry-lease.d.ts +62 -0
- package/lib/types/registry.d.ts +130 -19
- package/lib/types/roots.d.ts +1 -1
- package/lib/types/sandbox.d.ts +22 -2
- package/package.json +60 -13
- package/lib/{roots-DyT2jgEP.js → roots-DasWEBJk.js} +1 -1
package/CHANGELOG.en.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file records the user-visible changes of every **released** version.
|
|
4
|
+
|
|
5
|
+
中文:[CHANGELOG.md](./CHANGELOG.md)
|
|
6
|
+
|
|
7
|
+
- The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/).
|
|
8
|
+
- For installation sources, the supported upstream runtimes, and known limitations, see the [README](./README.en.md).
|
|
9
|
+
- The evidence, numbering, and release state of each version live in the [roadmap progress ledger](./docs/plans/active/2026-09-12-multi-root-workspace.md#进度总账). The body of each GitHub Release is generated from the commit history by `scripts/release-notes.mjs` and is the complete raw list behind this file.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
Nothing yet.
|
|
14
|
+
|
|
15
|
+
## [0.1.1] - 2026-09-16
|
|
16
|
+
|
|
17
|
+
The **hardening batch (H1–H4)** after `v0.1.0`: no new user-facing features, but four boundary gaps closed — cross-process authority, panel authority, upstream compatibility, and additional-root instructions. The reasoning lives in [ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md), [ADR-0008](./docs/decisions/ADR-0008-panel-session-derived-authority.md), [ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md), and [ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md).
|
|
18
|
+
|
|
19
|
+
### Upgrade Notes (Breaking Changes)
|
|
20
|
+
|
|
21
|
+
- **The first `dsh plugin add` fails once, from every install source**: this release introduces the native dependency `koffi` (the FFI wrapper behind Registry Authority on Windows), and pnpm ≥10 runs no dependency build script by default, so the first `add` exits with `[ERR_PNPM_IGNORED_BUILDS]` and the plugin never reaches `dsh.profile.bundles`. `dsh` leaves the pending decision in that profile's `pnpm-workspace.yaml` (`koffi: set this to true or false`); set it to `true` and run `add` again. The npm and tarball sources had no such step in `v0.1.0`. See [Troubleshooting: the install stops at build approval](./docs/troubleshooting/install-stops-at-build-approval.md).
|
|
22
|
+
- **The supported upstream runtimes narrow to the exact list `0.1.5-rc.2`, `0.1.6-alpha.1`** (they used to be the range `>=0.1.2-alpha.4 <0.2.0`). When the host release is neither of those, or several `@deepseek-ai/dsh-*` packages are mixed across versions, the `fs` / `sandbox` / `registry` / `instructions` rows **do not start**: the composition degrades to "this plugin is not installed" and prints an explanation at startup. The reason is that this plugin replaces the fence itself and works by recognizing argv shapes measured on specific upstream releases, so a semver range is a promise about versions nobody verified ([ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md)). The diagnosis steps are in [Troubleshooting: a DSH release outside the support matrix](./docs/troubleshooting/unsupported-dsh-release.md). **If you are still on the `0.1.2-rc.1` runtime that `v0.1.0` was measured against (for example a runtime bundled with an installed desktop app), upgrade that runtime first — otherwise this release has no effect.**
|
|
23
|
+
- **The panel no longer accepts a client-named root**: every panel endpoint requires `sessionId`, and the primary root comes only from the host-side `session.header.cwd`. The client `primaryRoot` field and the `sandboxPolicy` fallback are gone. When the browser has no active session, the panel shows "No active session" and does not call the host at all ([ADR-0008](./docs/decisions/ADR-0008-panel-session-derived-authority.md)).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Cross-process Registry Authority (H1)**: at most one DSH process may hold the root registry per `$DSH_HOME` (POSIX `flock` / Windows named semaphore, released by the kernel when the process exits or crashes). A contending process fails closed — it publishes an empty scope and registry mutations report `registry-contended`; once the holder exits, `/workspace-folders list` or a panel refresh takes over through `refresh()` ([ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md)).
|
|
28
|
+
- **An additional root's own instruction files now reach the model (H4)**: the root's **top-level** `AGENTS.md` / `CLAUDE.md` are injected before the first step of a session; instruction files in the subdirectories the session has **successfully** touched with `read` / `write` / `edit` (including every ancestor directory of a touched file, up to the additional root) are injected incrementally as those directories come up for examination; a changed file is re-sent on its own, and a file or root that disappears is **explicitly withdrawn**. All additional roots share a 64 KiB budget, and delivery is a `user`-role `{ kind: 'plugin', form: 'instructions' }` message (never `systemPrompt`). Upstream discovery walks upward from the session cwd and can never reach an additional root — that is why this exists ([ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md)).
|
|
29
|
+
- **Support for a second upstream runtime, `0.1.6-alpha.1`**, alongside the `0.1.5-rc.2` baseline; both have run the full matrix (the sync/async shape of `confine`, the renamed instruction renderer, and the two LLM wire protocols are absorbed by the adapter layer and the smoke scripts).
|
|
30
|
+
- **`pnpm compat:check`**: a consistency gate over the allowlist / `peerDependencies` / development pin / installed tree, run as the first CI step.
|
|
31
|
+
- **The `multi-root-compat` startup gate row**: the version verdict became a precondition for the other security-relevant rows instead of a log line.
|
|
32
|
+
- **A weekly upgrade lane** (`upgrade.yml`): it re-points the pin at the newest upstream pre-release and runs the full matrix, with read-only permissions, no commits, and no automatic widening of the allowlist.
|
|
33
|
+
- Two troubleshooting entries: [a DSH release outside the support matrix](./docs/troubleshooting/unsupported-dsh-release.md) and [the registry owned by another process](./docs/troubleshooting/registry-owned-by-another-process.md).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Version differences are concentrated in the `src/compat/` adapter layer and decided by **structural probes** (is the return value thenable? which export name exists?) rather than by comparing version strings; business code contains no version checks at all.
|
|
38
|
+
- The two optional peers (`@deepseek-ai/dsh-llm`, `@deepseek-ai/dsh-agent-instructions`) are now **loaded on demand**: the barrel's load-time dependency set equals the required-package set, so a minimal composition that never builds a message cannot fail while loading.
|
|
39
|
+
- `pnpm verify:all` deliberately **excludes** `compat:check` (the upgrade lane must run the full matrix on a candidate that is not on the allowlist yet); the main CI lane runs it separately, first.
|
|
40
|
+
- New `./compat` and `./instructions` subpath exports.
|
|
41
|
+
- Installation and release: the `prepare` script is now self-contained (git installs build on the spot through pnpm, after the user authorizes `allowBuilds`); the README documents all four installation sources (npm / tarball / local path / GitHub); npm publishing moved to **trusted publishing (OIDC)**, so the repository stores no npm credential at all.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Authority teardown order**: the old code released the kernel lease *before* closing the domain, so a successor process could acquire the lease and open a snapshot missing this process's last write. The whole teardown now runs inside the store-wide authority transition queue as "drain in-flight mutations → close the domain → release the lease", and sets `disposed` to refuse later acquisitions (`tests/registry-lease.spec.ts`).
|
|
46
|
+
- **Instruction delivery state is committed from the actual artifact**: a renderer that omits or truncates a file no longer records a full digest; a message that fails to build or is cancelled is no longer treated as delivered; a spent budget still runs withdrawals, so cross-root withdrawals and deferred touches are no longer skipped.
|
|
47
|
+
- **Instruction files in intermediate directories** of an additional root could never reach the model (only the touched file's parent directory was examined); every ancestor directory is now examined.
|
|
48
|
+
- **`DSH_MULTI_ROOT_COMPAT=warn` tightened**: it relaxes `unsupported` only (a consistent tree the allowlist does not name yet — exactly what the upgrade lane probes). `mixed` / `incomplete` are rejected in both modes.
|
|
49
|
+
- **"Package absent" is separated from "installed but failed to evaluate"**: the latter is no longer cached as an absence, so the process cannot silently stop delivering additional-root instructions.
|
|
50
|
+
- **Windows lock naming is normalized by physical path**: two processes reaching one store through a path alias (junction / symlink) used to hash different names and could both become the authority.
|
|
51
|
+
- **Session event pairing is scoped per session**: the same `tool/call` id in two sessions no longer overwrites the other session's pending call.
|
|
52
|
+
- **Shell input handling in the upgrade workflow**: the manual version and the candidate version travel through step `env` and are validated as a single line instead of being interpolated into `run`.
|
|
53
|
+
- **A local pack no longer carries earlier builds**: `lib/` is stripped of the previous build's JavaScript before `bundle` runs (tsdown's shared chunks are content-hashed, so an old name never disappears on its own). The tarball `pnpm pack` produces now contains exactly one build's artifacts instead of shipping historical chunks as dead code (`files` publishes every `lib/*.js`).
|
|
54
|
+
|
|
55
|
+
## [0.1.0] - 2026-09-13
|
|
56
|
+
|
|
57
|
+
The first release: the three MVP milestones (M1–M3).
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **Plugin skeleton and composition (M1)**: a bundle patch replaces the upstream `fs-sandbox` and `sandbox` provider rows and inserts the plugin's rows (`bash-sandbox` stays upstream — all bash and PTY confinement goes through `ctx.sandbox`). With no additional root configured, behavior is item-for-item identical to an uninstalled harness; the three structural risks (composition, disable/insert ordering, duplicate provides) are pinned by differential tests.
|
|
62
|
+
- **Multi-root capability (M2)**: `ctx.multiRootScope` (the single grant source), `MultiRootFileSystem` (the in-process containment fence, subclassing upstream's `LocalFileSystem`, which has no fence of its own), and `MultiRootSandboxProvider` (cloning the upstream grant spelling onto Seatbelt / bwrap / Landlock profiles for the additional roots). The fs side and bash/PTY share one scope; security does not degrade.
|
|
63
|
+
- **A model-visible topology snapshot**: emitted through `systemPrompt.context` only under `workspace-write` with a non-empty root set (same workspace, cwd unchanged); empty-root and read-only output stays byte-identical.
|
|
64
|
+
- **The root registry (M3)**: persisted in the `multi_root_workspace` storage domain keyed by the canonical primary root, validated on every write and every read; each record carries a `recordedPath`, and **re-resolving a path is not re-authorizing it** — a directory replaced by a symlink pointing elsewhere is marked `redirected` and stops being granted.
|
|
65
|
+
- **The `/workspace-folders` command**: `list` / `add <path>` (with no argument it opens the system directory picker) / `alias` / `remove` / `reveal`, all usable in headless compositions too.
|
|
66
|
+
- **The Workspace Folders browser panel**: a sidebar-foot action plus a self-drawn dialog talking to the host over the plugin's own Connection RPC channel; bilingual copy following the UI language, with every color taken from the host's `var(--dsw-*)` design tokens.
|
|
67
|
+
- **A cross-repository journey e2e** (`pnpm smoke:journey`): it drives a real agent turn in both the `web` and `headless` compositions across two Git repositories (primary root `repo-a`, additional root `repo-b`) and asserts that writes outside the roots are refused. No model credentials are needed (the model endpoint is scripted).
|
|
68
|
+
- **Installation sources**: npm registry / tarball / local path / GitHub; a tag-triggered `release.yml` runs verify → npm publish (with provenance) → GitHub Release.
|
|
69
|
+
- **Platform scope**: full kernel-level multi-root on macOS (Seatbelt) and Linux (bwrap / Landlock); Windows covers the `fs` write path, while multi-root for confined bash/PTY is out of scope for this phase (a one-time explicit warning is printed when the scope is non-empty).
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- All eight findings of the pre-release external review (three of them release-blocking) are fixed, each with a regression test: a registered directory replaced by a symlink transplanting its authority (which introduced `recordedPath` — **re-resolving a path is not re-authorizing it**), concurrent registry mutations losing a write or reviving a revoked grant (per-primary-root mutations are serialized), a refresh not re-checking directories, a typed path being overwritten by the directory picker, the `reveal` response contract and failure code, duplicate ids not validated, the primary root missing from nesting validation, and CI testing before building.
|
|
74
|
+
|
|
75
|
+
[Unreleased]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/compare/v0.1.1...HEAD
|
|
76
|
+
[0.1.1]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/compare/v0.1.0...v0.1.1
|
|
77
|
+
[0.1.0]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/tag/v0.1.0
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本文件记录每个**已发布**版本对使用者可见的变更。
|
|
4
|
+
|
|
5
|
+
English: [CHANGELOG.en.md](./CHANGELOG.en.md)
|
|
6
|
+
|
|
7
|
+
- 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
8
|
+
- 安装方式、受支持的上游运行时与已知限制见 [README](./README.md)。
|
|
9
|
+
- 每个版本的实施证据、编号口径与发布状态见[路线图 §进度总账](./docs/plans/active/2026-09-12-multi-root-workspace.md#进度总账);GitHub Release 的正文由 `scripts/release-notes.mjs` 从 commit 历史生成,是本文件之外的完整原始清单。
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
无。
|
|
14
|
+
|
|
15
|
+
## [0.1.1] - 2026-09-16
|
|
16
|
+
|
|
17
|
+
`v0.1.0` 之后的**硬化批次(H1–H4)**:不新增面向使用者的"功能",而是补齐跨进程、面板权威、上游兼容性与附加根指令四类边界。设计取舍见 [ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md)、[ADR-0008](./docs/decisions/ADR-0008-panel-session-derived-authority.md)、[ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md)、[ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md)。
|
|
18
|
+
|
|
19
|
+
### 升级注意(不兼容变更)
|
|
20
|
+
|
|
21
|
+
- **首次 `dsh plugin add` 会失败一次,四种安装来源皆然**:本版本新引入原生依赖 `koffi`(Windows 上 Registry Authority 的 FFI 封装),而 pnpm ≥10 默认不运行依赖的构建脚本,因此第一次 `add` 会以 `[ERR_PNPM_IGNORED_BUILDS]` 退出、插件不会进 `dsh.profile.bundles`。`dsh` 会把待决项留在该 profile 的 `pnpm-workspace.yaml` 里(`koffi: set this to true or false`),改成 `true` 再执行一次 `add` 即完成。`v0.1.0` 的 npm / tarball 来源没有这一步。详见[故障排查:安装停在构建授权](./docs/troubleshooting/install-stops-at-build-approval.md)。
|
|
22
|
+
- **支持的上游运行时收窄为精确清单 `0.1.5-rc.2`、`0.1.6-alpha.1`**(原先是 `>=0.1.2-alpha.4 <0.2.0` 范围)。宿主版本不在这两个之一,或若干 `@deepseek-ai/dsh-*` 包混装了不同版本时,`fs` / `sandbox` / `registry` / `instructions` 四行**不启动**,组合退化为"未装这个插件"并在启动时打印说明。理由是这个插件替换的是围栏本身、靠识别上游实测出的方言 argv 形状工作,而 npm 语义化范围等于对未验证版本做承诺([ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md))。诊断步骤见[故障排查:DSH 版本不在支持矩阵上](./docs/troubleshooting/unsupported-dsh-release.md)。**若你仍在 `v0.1.0` 时代实测可用的 `0.1.2-rc.1` 运行时上(例如某些已安装的桌面端自带运行时),请先升级该运行时,否则本版本不会生效。**
|
|
23
|
+
- **面板不再接受客户端指名的根**:所有面板端点必填 `sessionId`,主根只来自 host 侧的 `session.header.cwd`;客户端 `primaryRoot` 字段与 `sandboxPolicy` 回退已删除。浏览器当前没有活动会话时,面板显示"当前没有活动会话"并且不向 host 发请求([ADR-0008](./docs/decisions/ADR-0008-panel-session-derived-authority.md))。
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **跨进程 Registry Authority(H1)**:同一 `$DSH_HOME` 上同时只允许一个 DSH 进程持有根登记表(POSIX `flock` / Windows named semaphore,进程退出或崩溃由内核释放锁)。争用方 fail-closed——发布空 scope、注册表写操作报 `registry-contended`;持锁者退出后,`/workspace-folders list` 或面板刷新即经 `refresh()` 接管([ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md))。
|
|
28
|
+
- **附加根自身的指令文件进入模型上下文(H4)**:附加根**顶层**的 `AGENTS.md` / `CLAUDE.md` 在会话第一步之前注入;本会话**成功** `read` / `write` / `edit` 触碰过的子目录(含被触碰文件的每个祖先目录,直到该附加根)在其目录被考察到时增量补投;文件内容变化只重发该文件,文件或根消失时**显式撤回**。全部附加根共享 64 KiB 预算,以 `user` role 的 `{ kind: 'plugin', form: 'instructions' }` 消息投递(不是 `systemPrompt`)。上游的指令发现从会话 cwd 向上走,永远到不了附加根,这是本项存在的原因([ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md))。
|
|
29
|
+
- **支持第二个上游运行时 `0.1.6-alpha.1`**,与基线 `0.1.5-rc.2` 一起进 allowlist;两者都跑过完整矩阵(`confine` 的同步/异步形状差异、instruction renderer 改名、两种 LLM wire 协议均由适配层与 smoke 吸收)。
|
|
30
|
+
- **`pnpm compat:check`**:allowlist / `peerDependencies` / 开发 pin / 已安装树四者一致性门禁,是 CI 的第一步。
|
|
31
|
+
- **`multi-root-compat` 启动门禁行**:版本判定变成其余安全相关行的前置条件,而不是一条日志。
|
|
32
|
+
- **按周升级车道**(`upgrade.yml`):自动重指最新上游 pre-release 并跑完整矩阵,只读权限、不提交、不自动扩大 allowlist。
|
|
33
|
+
- 两个故障排查条目:[DSH 版本不在支持矩阵上](./docs/troubleshooting/unsupported-dsh-release.md)、[根登记表被另一个进程持有](./docs/troubleshooting/registry-owned-by-another-process.md)。
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- 上游版本差异集中在 `src/compat/` 适配层,一律按**结构探测**(返回值是否 thenable、导出的是哪个名字)判断,不比较版本字符串;业务代码不再出现任何版本判断。
|
|
38
|
+
- 两个可选 peer(`@deepseek-ai/dsh-llm`、`@deepseek-ai/dsh-agent-instructions`)改为**按需加载**:barrel 的加载期依赖集合等于必需包集合,一个从不需要构造消息的最小组合不会在加载时失败。
|
|
39
|
+
- `pnpm verify:all` 有意**不含** `compat:check`(升级车道要在一个还不在 allowlist 上的候选版本上跑完整矩阵);CI 主车道单独把它排在最前。
|
|
40
|
+
- 新增 `./compat` 与 `./instructions` 子路径导出。
|
|
41
|
+
- 安装与发版:`prepare` 脚本改为自包含(git 安装由 pnpm 现场构建,用户需先授权 `allowBuilds`);README 补齐 npm / tarball / 本地路径 / GitHub 四种安装来源;npm 发布改用 **trusted publishing(OIDC)**,仓库不再保存任何 npm 凭证。
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **注册表拆除顺序**:原先"先释放内核 lease 再关闭 domain",继任进程可能拿到 lease 后打开一份缺了本进程最后一次写入的快照;现在整个拆除在 store-wide authority 转场队列内按"排空在飞 mutation → 关闭 domain → 释放 lease"执行,并置 `disposed` 拒绝后续 acquisition(`tests/registry-lease.spec.ts`)。
|
|
46
|
+
- **指令投递状态按实际产物提交**:renderer 省略或截断文件时不再记录完整 digest;消息构造失败或被取消不再把指令当成已送达;预算耗尽后仍检查撤回,跨根撤回与延迟触碰不再被跳过。
|
|
47
|
+
- **附加根中间目录的指令文件**此前永远到不了模型(只考察被触碰文件的父目录),现在其每个祖先目录都会被考察。
|
|
48
|
+
- **`DSH_MULTI_ROOT_COMPAT=warn` 收紧**:只放宽 `unsupported`(版本一致、allowlist 未列名——升级车道要探测的正是这一种),`mixed` / `incomplete` 在两种模式下都拒绝。
|
|
49
|
+
- **"包不存在"与"已安装但求值失败"分离**:后者不再被当成缺失缓存,进程不会静默停止投递附加根指令。
|
|
50
|
+
- **Windows 锁命名按物理路径归一化**:经路径别名(junction / 符号链接)访问同一 store 的两个进程此前会各持一把锁,可同时成为 authority。
|
|
51
|
+
- **会话事件配对按会话分隔**:不同会话使用相同 `tool/call` id 时不再互相覆盖、把目录记到另一个会话。
|
|
52
|
+
- **升级工作流的 shell 输入处理**:手动版本与候选版本经 step `env` 传值并校验为单行,不再拼进 `run` 源码。
|
|
53
|
+
- **本地打包不再夹带历史构建产物**:`lib/` 在 `bundle` 之前先清掉上一轮的 JS 面(tsdown 的共享 chunk 按内容哈希命名,旧名字不会自己消失)。`pnpm pack` 出的 tarball 现在只含一轮构建的制品,不再把历史 chunk 当死代码发出去(`files` 里的 `lib/*.js` 会全部进包)。
|
|
54
|
+
|
|
55
|
+
## [0.1.0] - 2026-09-13
|
|
56
|
+
|
|
57
|
+
首个发布版本:MVP 的三个里程碑(M1–M3)。
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **插件骨架与组合方式(M1)**:以 bundle patch 替换上游 `fs-sandbox` 与 `sandbox` 两行 provider 并插入本插件的行(`bash-sandbox` 保持上游——bash 与 PTY 的 confinement 全部经 `ctx.sandbox`)。未配置附加根时行为与未装插件逐项一致;组合、disable/insert 时序与 provide 冲突三类结构性风险由差分测试钉住。
|
|
62
|
+
- **多根能力(M2)**:`ctx.multiRootScope`(唯一授权源)、`MultiRootFileSystem`(进程内 containment fence,子类自上游 `LocalFileSystem`,`LocalFileSystem` 自身没有 fence)、`MultiRootSandboxProvider`(在 Seatbelt / bwrap / Landlock 三种方言的 profile 上克隆上游 grant 拼法追加附加根);fs 与 bash/PTY 共享同一条 scope,安全不降级。
|
|
63
|
+
- **模型可见的拓扑快照**:仅 `workspace-write` + 非空附加根时经 `systemPrompt.context` 输出(同 workspace、cwd 不变);空根与只读模式逐字节不变。
|
|
64
|
+
- **根注册表(M3)**:以 canonical 主根为键持久化在 `multi_root_workspace` storage domain,写入与读取都做校验;每条记录带 `recordedPath`,**重新解析路径不等于重新授权**——目录被替换成指向别处的符号链接时该根标为 `redirected` 并暂不授予。
|
|
65
|
+
- **`/workspace-folders` 命令**:`list` / `add <路径>`(无参数时打开系统目录选择器)/ `alias` / `remove` / `reveal`,headless 下同样可用。
|
|
66
|
+
- **Workspace Folders 浏览器面板**:侧栏底部动作 + 自绘对话框,经插件自己的 Connection RPC 通道与 host 通信;文案中英双语跟随界面语言,配色全部使用 host 的 `var(--dsw-*)` 设计 token。
|
|
67
|
+
- **跨仓库旅程 e2e**(`pnpm smoke:journey`):在 `web` 与 `headless` 两种组合下各驱动一轮真实 agent 回合,跨两个 Git 仓库(主根 `repo-a`、附加根 `repo-b`)并断言根外写入被拒。无需模型凭据(脚本化模型端点)。
|
|
68
|
+
- **安装形态**:npm 注册表 / tarball / 本地路径 / GitHub;tag 触发的 `release.yml` 完成 verify → npm publish(provenance)→ GitHub Release。
|
|
69
|
+
- **平台范围**:macOS(Seatbelt)与 Linux(bwrap / Landlock)的内核级多根全量;Windows 覆盖 `fs` 写路径,受限 bash/PTY 的多根不在本期(非空 scope 时输出一次显式告警)。
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- 发布前外部评审提出的 8 项发现(其中 3 项发布阻断)全部修复,每项各带一个回归测试:登记目录被替换为符号链接后授权转移(引入 `recordedPath`,**重新解析路径不等于重新授权**)、并发修改注册表丢写或复活已撤销的授权(每个主根的变更串行)、刷新不重新检查目录、手输路径被目录选择器覆盖、`reveal` 的应答契约与失败码、重复 id 未校验、主根未纳入嵌套校验、CI 先测后构建。
|
|
74
|
+
|
|
75
|
+
[Unreleased]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/compare/v0.1.1...HEAD
|
|
76
|
+
[0.1.1]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/compare/v0.1.0...v0.1.1
|
|
77
|
+
[0.1.0]: https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/tag/v0.1.0
|
package/README.en.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-plugin-multi-root-workspace
|
|
2
2
|
|
|
3
|
+
[中文](./README.md) | English
|
|
4
|
+
|
|
3
5
|
## What & Why
|
|
4
6
|
|
|
5
7
|
An out-of-tree plugin bundle for DSH (DeepSeek Harness) that widens the Workspace write scope from a single canonical directory to "**one primary root + N additional roots**" — **without modifying any package in the upstream repository**.
|
|
@@ -12,15 +14,18 @@ Three things make this plugin worth looking at:
|
|
|
12
14
|
- **Security does not degrade**: the additional roots are granted through the same mechanisms the harness already uses — the in-process fs fence plus kernel-level runners (Seatbelt on macOS, bwrap / Landlock on Linux) — with the fs side and bash/PTY sharing one and the same scope. It never degenerates into danger-full-access or prompt-level constraints.
|
|
13
15
|
- **Absent when absent**: it replaces the upstream `fs-sandbox` and `sandbox` provider rows through a bundle patch; with no additional root configured its behavior is item-for-item identical to an uninstalled harness, and every misconfiguration fails loudly instead of silently degrading.
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
**Done and released: the MVP `v0.1.0` (milestones M1–M3)** — composition and empty-root pass-through, multi-root capability and dialect grants, the root registry / the `/workspace-folders` command / the Workspace Folders panel / a cross-repository journey e2e.
|
|
18
|
+
|
|
19
|
+
**Done and released: the `v0.1.1` hardening batch (H1–H4)** — a cross-process Registry Authority Lease (when two DSH processes share one `$DSH_HOME`, only the lease holder grants additional roots; the other fails closed and takes over once the holder exits), panel primary-root authority derived from the host session (a client-named root is no longer accepted), DSH compatibility turned from a documented agreement into a **code contract enforced at startup** (an exact allowlist, mixed-install detection, a `src/compat/` adapter layer — the supported set narrows to `0.1.5-rc.2` and `0.1.6-alpha.1`, see the "Upgrade Notes" in the [CHANGELOG](./CHANGELOG.en.md)), and **an additional root's own `AGENTS.md` / `CLAUDE.md` reaching the model** — native instruction discovery walks upward from the session cwd, so it can never reach an additional root.
|
|
20
|
+
|
|
21
|
+
The single source of truth for progress, numbering, and release state is the [roadmap progress ledger](./docs/plans/active/2026-09-12-multi-root-workspace.md#进度总账) (M1–M4 are the MVP milestone numbers, H1–H4 are the `v0.1.1` batch numbers, and H1 is M4); per-item evidence lives in the [completed plans](./docs/plans/README.md), and the user-visible changes of each version are in the [CHANGELOG](./CHANGELOG.en.md).
|
|
16
22
|
|
|
17
23
|
## Quick Start
|
|
18
24
|
|
|
19
|
-
With a DSH runtime at hand (web / Electron desktop / headless all work), the shortest path is
|
|
25
|
+
With a DSH runtime at hand (web / Electron desktop / headless all work), the shortest path is two commands — the plugin is published on [npm](https://www.npmjs.com/package/@dsh-electron/dsh-plugin-multi-root-workspace):
|
|
20
26
|
|
|
21
27
|
```sh
|
|
22
|
-
|
|
23
|
-
dsh plugin --profile web add ./dsh-plugin-multi-root-workspace
|
|
28
|
+
dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
|
|
24
29
|
dsh --profile web
|
|
25
30
|
```
|
|
26
31
|
|
|
@@ -30,19 +35,78 @@ Once it is up, the **Folders** action (`🗂`) appears at the sidebar foot — o
|
|
|
30
35
|
/workspace-folders add ~/code/another-repo
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
The agent can now read, write, and run bash in that directory, with the same rights as this session's workspace.
|
|
38
|
+
The agent can now read, write, and run bash in that directory, with the same rights as this session's workspace. Other install sources (the GitHub repository / a local clone) and the command differences of running DSH from a source checkout are covered under [Installation](#installation).
|
|
34
39
|
|
|
35
40
|
## Requirements
|
|
36
41
|
|
|
37
|
-
- **
|
|
38
|
-
- **pnpm 11** (`packageManager` pins `pnpm@11.25.0`; corepack recommended)
|
|
39
|
-
- **DSH runtime `0.1.5-rc.2
|
|
40
|
-
- **Platforms**: kernel-level multi-root is complete on macOS (Seatbelt) and Linux (bwrap or Landlock); on Windows only the `fs` write path covers additional roots (confined bash/PTY does not — see [known limitations](#known-limitations
|
|
41
|
-
- Running the smoke tests needs **no model credentials**: the e2e model turns are served by an inline scripted
|
|
42
|
+
- **Using the published plugin**: you need a *supported* DSH runtime — currently **`0.1.5-rc.2` and `0.1.6-alpha.1`**, and nothing else will install or run (see below). `dsh plugin` installs the package into the matching profile, and no local Node toolchain is required
|
|
43
|
+
- **Building from source / contributing**: **Node.js** `^22.19.0 || >=24` (pinned by the repository's `engines`), **Git**, and **pnpm 11** (`packageManager` pins `pnpm@11.25.0`; corepack recommended)
|
|
44
|
+
- **DSH runtime**: the dev pin is exactly `0.1.5-rc.2`, the baseline among the supported releases; the upgrade procedure lives in the [development workflow](./docs/development/plugin-development-workflow.md)
|
|
45
|
+
- **Platforms**: kernel-level multi-root is complete on macOS (Seatbelt) and Linux (bwrap or Landlock); on Windows only the `fs` write path covers additional roots (confined bash/PTY does not — see [known limitations](#known-limitations))
|
|
46
|
+
- Running the smoke tests needs **no model credentials**: the e2e model turns are served by an inline scripted model endpoint
|
|
47
|
+
|
|
48
|
+
**The supported DSH versions are an exact list, not a range.** This plugin replaces `ctx.fs` and `ctx.sandbox` — the security boundary itself — and it recognizes kernel sandbox dialects from argv shapes measured against specific upstream releases. So `peerDependencies` names only the releases that have actually been through the full verification, and startup checks again: if the host's release is not on the list, or several `@deepseek-ai/dsh-*` packages disagree about which release they are, the fs / sandbox / registry / instructions rows **do not start**, the composition degrades to "this plugin is not installed", and one explanatory line is logged. To diagnose, see [troubleshooting: unsupported DSH release](./docs/troubleshooting/unsupported-dsh-release.md); for the reasoning, [ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md).
|
|
42
49
|
|
|
43
50
|
## Installation
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
`dsh plugin` supports four install sources. All commands below use the web profile as the example; for the Electron desktop swap `--profile web` for `--profile desktop` — the install method is the same.
|
|
53
|
+
|
|
54
|
+
| Source | Command | Notes |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| npm registry (recommended) | `dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace` | Pre-built artifacts, ready to use; the first `add` needs one `allowBuilds` answer (below) |
|
|
57
|
+
| tarball | `dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-<version>.tgz` | Pre-built offline package, handy for air-gapped or offline delivery — with the same one-time `allowBuilds` answer |
|
|
58
|
+
| local path | `dsh plugin --profile web add /path/to/package/dsh-plugin-multi-root-workspace` | pnpm `link:` to a local checkout; its dependencies are already installed in this repository — best for development |
|
|
59
|
+
| GitHub / git | `dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace` | Pulls source and builds it on the spot through `prepare`; needs **two** `allowBuilds` answers (this package plus `koffi`); pin a tag |
|
|
60
|
+
|
|
61
|
+
**Every install source needs one `allowBuilds` answer the first time.** The plugin carries a native dependency that must be built, `koffi` (the FFI wrapper behind Registry Authority on Windows; installed but unused elsewhere), and pnpm ≥10 runs no dependency lifecycle script by default. So the first `dsh plugin add` fails with `[ERR_PNPM_IGNORED_BUILDS]` and leaves the pending decision in that profile's `pnpm-workspace.yaml`:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
allowBuilds:
|
|
65
|
+
koffi: true # the placeholder dsh leaves is "set this to true or false"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Set it to `true` and run `add` again. Read that line as **letting the dependency's install script run on your machine** (outside any sandbox the agent runs in) — `koffi`'s script only precompiles locally. Diagnosis steps: [Troubleshooting: the install stops at build approval](./docs/troubleshooting/install-stops-at-build-approval.md).
|
|
69
|
+
|
|
70
|
+
### Install from npm (recommended)
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This installs pre-built artifacts (the plugin itself is never compiled), but the first install still needs the `allowBuilds` answer above.
|
|
77
|
+
|
|
78
|
+
### Install from a tarball
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pnpm pack @dsh-electron/dsh-plugin-multi-root-workspace
|
|
82
|
+
# or download the tgz from the GitHub Release assets, e.g.:
|
|
83
|
+
# https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/download/v0.1.1/dsh-electron-dsh-plugin-multi-root-workspace-0.1.1.tgz
|
|
84
|
+
dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-0.1.1.tgz
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Also pre-built (the plugin itself is never compiled here), handy for air-gapped or offline delivery — and the first `add` needs the same `allowBuilds` answer.
|
|
88
|
+
|
|
89
|
+
### Install from GitHub
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
With pnpm ≥10 the first `add` fails: a git install pulls **source code rather than build artifacts**, so the package's self-contained `prepare` script must build it on the spot (a direct transpile of `src/`, no type-checking). Follow `dsh`'s guidance and copy the exact package keys pnpm prints into the profile's `pnpm-workspace.yaml` — this source has **two** pending decisions:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
allowBuilds:
|
|
99
|
+
'@dsh-electron/dsh-plugin-multi-root-workspace': true
|
|
100
|
+
koffi: true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then run `add` again. Pinning a tag (e.g. `#v0.1.1`) is recommended so a later push cannot silently change what actually runs:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace#v0.1.1
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Install from a local clone (development & debugging)
|
|
46
110
|
|
|
47
111
|
```sh
|
|
48
112
|
git clone https://github.com/cherrchen/dsh-plugin-multi-root-workspace.git
|
|
@@ -50,8 +114,11 @@ cd dsh-plugin-multi-root-workspace
|
|
|
50
114
|
export CI=true # without a TTY, pnpm's dependency self-check aborts; see the development workflow §8
|
|
51
115
|
pnpm install
|
|
52
116
|
pnpm build # produces lib/ (not tracked by Git); the artifact test and installation both need it
|
|
117
|
+
dsh plugin --profile web add "$PWD"
|
|
53
118
|
```
|
|
54
119
|
|
|
120
|
+
> **Tip**: if your DSH comes from a source checkout (rather than `npm install -g @deepseek-ai/deepseek-harness`), the `dsh` binary is not on the global PATH — replace `dsh` with `pnpm dsh` in the commands above, e.g. `pnpm dsh plugin --profile web add ...` and `pnpm dsh --profile web`.
|
|
121
|
+
|
|
55
122
|
## Running
|
|
56
123
|
|
|
57
124
|
Development gates, with the result each step should produce:
|
|
@@ -60,7 +127,7 @@ Development gates, with the result each step should produce:
|
|
|
60
127
|
pnpm lint && pnpm typecheck # expect: 0 warnings, 0 errors; both tsconfigs pass
|
|
61
128
|
pnpm test # expect: all pass; dialect cases without a local kernel runner skip explicitly, with a reason
|
|
62
129
|
pnpm kernel:probe # expect: reports the kernel runners this host has (seatbelt / bwrap / landlock)
|
|
63
|
-
pnpm smoke # expect: compose
|
|
130
|
+
pnpm smoke # expect: compose 40/40, behavior 99/99, journey 55/55
|
|
64
131
|
pnpm docs:check # expect: 0 errors, 0 warnings
|
|
65
132
|
```
|
|
66
133
|
|
|
@@ -71,7 +138,7 @@ dsh plugin --profile web add "$PWD"
|
|
|
71
138
|
dsh --profile web --dump-config
|
|
72
139
|
```
|
|
73
140
|
|
|
74
|
-
Expected: **only** the `fs-sandbox` and `sandbox` rows are replaced by the plugin's `multi-root-fs` / `multi-root-sandbox`, with
|
|
141
|
+
Expected: **only** the `fs-sandbox` and `sandbox` rows are replaced by the plugin's `multi-root-fs` / `multi-root-sandbox`, with eight rows inserted (`multi-root-compat` / `fs` / `sandbox` / `scope` / `registry` / `instructions` / `command`, plus the client carrier row `multi-root-client`); `bash-sandbox` stays upstream (bash and the PTY backend take their roots from `ctx.sandbox`). After starting `dsh --profile web`, the Folders action appears at the sidebar foot.
|
|
75
142
|
|
|
76
143
|
## Usage
|
|
77
144
|
|
|
@@ -104,16 +171,21 @@ Writable additional roots: 2 of 2.
|
|
|
104
171
|
src/
|
|
105
172
|
roots.ts pure rules: canonicalization, conflict validation, record classification (available/missing/redirected/invalid)
|
|
106
173
|
registry.ts the root registry: dsh-storage-domain persistence, mutations of one primary root serialized in one queue
|
|
174
|
+
registry-lease.ts / registry-lease-win32.ts
|
|
175
|
+
cross-process Registry Authority: POSIX flock and a Windows named semaphore (the kernel releases it on process death)
|
|
107
176
|
scope.ts ctx.multiRootScope: the single authorization source, plus the model-visible topology snapshot
|
|
108
177
|
fs.ts the multi-root filesystem provider (in-process fence, extends the upstream LocalFileSystem)
|
|
109
178
|
sandbox.ts the multi-root kernel-sandbox provider (extends the upstream LocalSandboxProvider)
|
|
110
179
|
dialects.ts Seatbelt / bwrap / Landlock profile recognition and additional-grant assembly (unrecognized shapes fail loudly)
|
|
111
180
|
containment.ts path containment (lexical fast path plus a dev/ino alias fallback)
|
|
181
|
+
instructions.ts discovery, incremental delivery, budget and revocation for an additional root's AGENTS.md / CLAUDE.md (top level plus the subdirectories this session touched, injected from agent/pre-step)
|
|
182
|
+
compat.ts the multi-root-compat startup gate (version allowlist and mixed-install detection)
|
|
183
|
+
compat/ the version-difference adapters: dsh-version / sandbox-confine / agent-instructions / llm-message (optional peers loaded on demand)
|
|
112
184
|
command.ts the /workspace-folders command and the host half of the panel RPC
|
|
113
185
|
contract.ts the panel wire protocol (zod-validated on both ends, inlinable into the browser bundle)
|
|
114
186
|
client/ the browser half: sidebar action, dialog, bilingual dictionaries
|
|
115
|
-
tests/ differential parity, real-execution dialect matrix, contract round-trips, component and locale gates
|
|
116
|
-
scripts/ smoke batteries (compose / behavior / journey) plus the docs
|
|
187
|
+
tests/ differential parity, real-execution dialect matrix, contract round-trips, component and locale gates, cross-process lease e2e, instruction injection
|
|
188
|
+
scripts/ smoke batteries (compose / behavior / journey) plus the docs, kernel-runner, compatibility-contract and upgrade checks
|
|
117
189
|
docs/ requirements, architecture, decision records (ADRs), plans, development workflow
|
|
118
190
|
```
|
|
119
191
|
|
|
@@ -121,24 +193,28 @@ docs/ requirements, architecture, decision records (ADRs), plans, de
|
|
|
121
193
|
|
|
122
194
|
Issues and PRs are welcome:
|
|
123
195
|
|
|
124
|
-
1. Read [`AGENTS.md`](./AGENTS.md) (repository-wide rules) and the [development workflow](./docs/development/plugin-development-workflow.md) (environment, commands, the
|
|
196
|
+
1. Read [`AGENTS.md`](./AGENTS.md) (repository-wide rules) and the [development workflow](./docs/development/plugin-development-workflow.md) (environment, commands, the runtime support matrix, the upgrade procedure) first.
|
|
125
197
|
2. Branch from `main`; follow conventional commits (`feat` / `fix` / `perf` / `refactor` + scope) — see the existing history.
|
|
126
198
|
3. All of these must pass locally before a PR: `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm test`, `pnpm docs:check`, `pnpm smoke` (CI runs the same order, build before test).
|
|
127
199
|
4. Behavior changes update the matching document under `docs/` in the same change; keep the README bilingual pair in sync; record engineering decisions that involve trade-offs as ADRs.
|
|
128
200
|
5. Hard constraint: **never modify any package of the upstream repository (deepseek-harness)**; the plugin stays an out-of-tree extension.
|
|
129
201
|
|
|
130
|
-
## Known Limitations
|
|
202
|
+
## Known Limitations
|
|
131
203
|
|
|
204
|
+
- An additional root's instruction files arrive as they become relevant: the one at the root's top level is injected before the session's first step, and a subdirectory's is injected once the session has **successfully** worked in that directory. A nested file is re-examined only when its directory is examined (the root's own every step; a subdirectory's because it was delivered from, or because of a fresh touch), delivery state is in-process (so a resumed session may be told again), and only the `read` / `write` / `edit` tool names count. The primary root's chain and the user-global file remain upstream's job, and this plugin does not inject them a second time ([ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md)).
|
|
205
|
+
- Single writer per store: only one DSH process at a time may hold the root registry for a given `$DSH_HOME`; another process reports the registry as unavailable (`registry-contended`) and takes over on refresh once the holder exits or crashes. That is deliberate fail-closed behavior, not a race waiting to be fixed ([ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md)).
|
|
206
|
+
- The supported matrix is an exact-version allowlist: on a host whose release is not on the list, or whose core packages disagree about the release, the `fs` / `sandbox` / `registry` / `instructions` rows **do not start** and the composition degrades to "the plugin is not installed" ([ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md)).
|
|
132
207
|
- Kernel-level multi-root does not exist on Windows: the `fs` write path covers additional roots, but confined bash/PTY cannot write them (the plugin emits one explicit warning for a populated scope); see the first-release scope in the [requirements document](./docs/requirements/multi-root-workspace.md).
|
|
133
208
|
- An additional root has the same rights as the primary root (no per-root read-only), and cannot become the default working directory of bash/PTY (session cwd semantics are unchanged).
|
|
134
209
|
- The `workspace-files` client file tree still sees the primary root only.
|
|
135
210
|
- The command's own output text is English (a host-side handler has no active locale to consult); the panel is bilingual.
|
|
136
|
-
- The Workspace Folders panel is a sidebar footer action plus a dialog rather than a full panel:
|
|
211
|
+
- The Workspace Folders panel is a sidebar footer action plus a dialog rather than a full panel: `sidebar.footer.action` is offered by every supported release, whereas `sidebar.panellist` / `main` are not.
|
|
137
212
|
|
|
138
213
|
## Documentation
|
|
139
214
|
|
|
140
215
|
Long-term project documentation lives in [`docs/`](./docs/README.md):
|
|
141
216
|
|
|
217
|
+
- [Changelog](./CHANGELOG.en.md)
|
|
142
218
|
- [Requirements](./docs/requirements/multi-root-workspace.md)
|
|
143
219
|
- [Target architecture](./docs/architecture/multi-root-workspace.md)
|
|
144
220
|
- [Upstream research](./docs/reference/multi-root-workspace-research.md)
|
|
@@ -149,8 +225,6 @@ Long-term project documentation lives in [`docs/`](./docs/README.md):
|
|
|
149
225
|
|
|
150
226
|
Repository-wide rules for Coding Agents are defined in [`AGENTS.md`](./AGENTS.md).
|
|
151
227
|
|
|
152
|
-
Chinese documentation: [`README.md`](./README.md)
|
|
153
|
-
|
|
154
228
|
## License
|
|
155
229
|
|
|
156
230
|
[MIT](./LICENSE)
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-plugin-multi-root-workspace
|
|
2
2
|
|
|
3
|
+
中文 | [English](./README.en.md)
|
|
4
|
+
|
|
3
5
|
## 项目简介(What & Why)
|
|
4
6
|
|
|
5
7
|
DSH(DeepSeek Harness)的外部插件 bundle:把 Workspace 的可写范围从"一个 canonical 目录"扩展为"**一个主根 + N 个附加根**",且**不修改上游仓库任何包**。
|
|
@@ -12,18 +14,23 @@ DSH(DeepSeek Harness)的外部插件 bundle:把 Workspace 的可写范围
|
|
|
12
14
|
- **安全不降级**:多根授权走上游同款机制——进程内 fs fence + 内核级 runner(macOS Seatbelt / Linux bwrap / Landlock),fs 与 bash/PTY 共享同一条 scope;绝不退化为 danger-full-access 或提示词约束。
|
|
13
15
|
- **不装就当不存在**:以 bundle patch 替换上游 `fs-sandbox` 与 `sandbox` 两行 provider;未配置附加根时行为与未装插件逐项一致,misconfiguration 一律响亮报错,从不静默降级。
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
**已完成并发布:MVP `v0.1.0`(里程碑 M1–M3)**——组合与空根直通、多根能力与方言 grant、根注册表 / `/workspace-folders` 命令 / Workspace Folders 面板 / 跨仓库旅程 e2e。
|
|
18
|
+
|
|
19
|
+
**已完成并发布:`v0.1.1` 硬化批次(H1–H4)**——跨进程 Registry Authority Lease(两个 DSH 进程共用 `$DSH_HOME` 时只有持锁者授予附加根,另一方 fail-closed 并在对方退出后接管)、面板主根改为 host session 推导(不再接受客户端指名的根)、DSH 兼容性从文档约定变成**启动时执行的代码契约**(精确 allowlist + 混装检测 + `src/compat/` 适配层,支持范围收窄为 `0.1.5-rc.2` 与 `0.1.6-alpha.1`,见 [CHANGELOG](./CHANGELOG.md) 的「升级注意」),以及**附加根自身的 `AGENTS.md` / `CLAUDE.md` 进入模型上下文**——原生指令发现从会话 cwd 向上走,永远到不了附加根。
|
|
20
|
+
|
|
21
|
+
进度、编号与发布状态的唯一真源是[路线图 §进度总账](./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)。
|
|
16
22
|
|
|
17
23
|
## 快速开始
|
|
18
24
|
|
|
19
|
-
已有 DSH 运行时(web / Electron 桌面 / headless
|
|
25
|
+
已有 DSH 运行时(web / Electron 桌面 / headless 均可)时,最短路径是两步——插件已发布到 [npm](https://www.npmjs.com/package/@dsh-electron/dsh-plugin-multi-root-workspace):
|
|
20
26
|
|
|
21
27
|
```sh
|
|
22
|
-
|
|
23
|
-
dsh plugin --profile web add ./dsh-plugin-multi-root-workspace
|
|
28
|
+
dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
|
|
24
29
|
dsh --profile web
|
|
25
30
|
```
|
|
26
31
|
|
|
32
|
+
其他安装来源(GitHub 仓库 / 本地源码)与源码方式运行 DSH 的命令差异,见[安装](#安装)。
|
|
33
|
+
|
|
27
34
|
启动后侧栏底部出现 **Folders**(`🗂`)动作,或直接在会话里:
|
|
28
35
|
|
|
29
36
|
```text
|
|
@@ -34,15 +41,74 @@ dsh --profile web
|
|
|
34
41
|
|
|
35
42
|
## 环境要求
|
|
36
43
|
|
|
37
|
-
-
|
|
38
|
-
- **pnpm 11**(`packageManager` 钉 `pnpm@11.25.0`,建议经 corepack 启用)
|
|
39
|
-
- **DSH
|
|
44
|
+
- **使用已发布的插件**:需要一个受支持的 DSH 运行时 —— 当前是 **`0.1.5-rc.2` 与 `0.1.6-alpha.1`**,别的版本装不上也不会跑(见下)。`dsh plugin` 会把包装进对应 profile,无需本地 Node 工具链
|
|
45
|
+
- **从源码构建 / 参与**:**Node.js** `^22.19.0 || >=24`(仓库 `engines` 钉住)、**Git**、**pnpm 11**(`packageManager` 钉 `pnpm@11.25.0`,建议经 corepack 启用)
|
|
46
|
+
- **DSH 运行时**:开发依赖精确 pin 在 `0.1.5-rc.2`(受支持版本里的基线);升级流程见[开发工作流](./docs/development/plugin-development-workflow.md)
|
|
40
47
|
- **平台支持**:macOS(Seatbelt)与 Linux(bwrap 或 Landlock)内核级多根全量;Windows 仅 `fs` 写路径覆盖附加根(受限 bash/PTY 不含,见[已知限制](#已知限制第一期))
|
|
41
|
-
- 运行冒烟测试**不需要模型凭据**:e2e
|
|
48
|
+
- 运行冒烟测试**不需要模型凭据**:e2e 的模型轮次由内联的脚本化模型端点提供
|
|
49
|
+
|
|
50
|
+
**受支持的 DSH 版本是一份精确清单,不是一个范围。** 因为本插件替换的是 `ctx.fs` 与 `ctx.sandbox`——安全边界本身——而它识别内核沙箱方言靠的是对具体上游版本实测出来的 argv 形状。所以 `peerDependencies` 只列真正跑过全套验证的版本,启动时也会再查一遍:宿主的版本不在清单上,或者若干个 `@deepseek-ai/dsh-*` 混装了不同版本,那么 fs / sandbox / registry / instructions 四行**不启动**,组合退化成"没装这个插件",并打印一条说明。诊断办法见[故障排查:DSH 版本不在支持矩阵上](./docs/troubleshooting/unsupported-dsh-release.md),理由见 [ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md)。
|
|
51
|
+
|
|
52
|
+
## 安装
|
|
53
|
+
|
|
54
|
+
`dsh plugin` 支持四种安装来源。以下均以 web profile 为例;桌面端(Electron)把 `--profile web` 换成 `--profile desktop`,安装方式同源。
|
|
55
|
+
|
|
56
|
+
| 来源 | 命令 | 说明 |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| npm 注册表(推荐) | `dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace` | 预构建产物,即装即用;首次 `add` 需要回答一次 `allowBuilds`(见下) |
|
|
59
|
+
| tarball | `dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-<version>.tgz` | 预构建离线包,适合内网/离线;同样需要那一次 `allowBuilds` 回答 |
|
|
60
|
+
| 本地路径 | `dsh plugin --profile web add /path/to/package/dsh-plugin-multi-root-workspace` | pnpm `link:` 链接本地 checkout,依赖已在本仓库装好,适合开发调试 |
|
|
61
|
+
| GitHub / git | `dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace` | 拉源码由 `prepare` 现场构建;需要两次 `allowBuilds` 回答(本包 + `koffi`),建议锁定 tag |
|
|
62
|
+
|
|
63
|
+
**首次安装需要回答一次 `allowBuilds`,四种来源皆然。** 本插件带一个需要构建的原生依赖 `koffi`(Windows 上 Registry Authority 用的 FFI 封装;其他平台装了不用),而 pnpm ≥10 默认不运行任何依赖的生命周期脚本。所以第一次 `dsh plugin add` 会以 `[ERR_PNPM_IGNORED_BUILDS]` 失败,并把待决项写进该 profile 的 `pnpm-workspace.yaml`:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
allowBuilds:
|
|
67
|
+
koffi: true # dsh 留的占位是 "set this to true or false"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
把它改成 `true` 再执行一次 `add` 即可。把这一行理解为**允许该依赖的安装脚本在你的机器上执行**(不在 agent 运行的任何沙箱之内)——`koffi` 的脚本只做本地预编译。诊断步骤见[故障排查:安装停在构建授权](./docs/troubleshooting/install-stops-at-build-approval.md)。
|
|
71
|
+
|
|
72
|
+
### 从 npm 安装(推荐)
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
dsh plugin --profile web add @dsh-electron/dsh-plugin-multi-root-workspace
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
安装的是预构建产物(不需要编译本插件本身),但首次仍要回答上面那一次 `allowBuilds`。
|
|
42
79
|
|
|
43
|
-
|
|
80
|
+
### 从 tarball 安装
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
```sh
|
|
83
|
+
pnpm pack @dsh-electron/dsh-plugin-multi-root-workspace
|
|
84
|
+
# 或从 GitHub Release 资产下载,例如:
|
|
85
|
+
# https://github.com/cherrchen/dsh-plugin-multi-root-workspace/releases/download/v0.1.1/dsh-electron-dsh-plugin-multi-root-workspace-0.1.1.tgz
|
|
86
|
+
dsh plugin --profile web add ./dsh-electron-dsh-plugin-multi-root-workspace-0.1.1.tgz
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
同样是预构建产物(不需要编译本插件本身),适合内网或离线环境交付;首次 `add` 同样要回答那一次 `allowBuilds`。
|
|
90
|
+
|
|
91
|
+
### 从 GitHub 安装
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
pnpm ≥10 下首次 `add` 会失败:git 安装拉取的是**源码而非构建产物**,包内自包含的 `prepare` 脚本要现场构建(直接转译 `src/`,不做类型检查)。按 `dsh` 的提示把 pnpm 打印的包键写入该 profile 的 `pnpm-workspace.yaml`——这条来源要回答**两个**待决项:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
allowBuilds:
|
|
101
|
+
'@dsh-electron/dsh-plugin-multi-root-workspace': true
|
|
102
|
+
koffi: true
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
然后重新执行 `add` 即可。建议锁定 tag(如 `#v0.1.1`),让后续推送无法悄悄改变实际运行的内容:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
dsh plugin --profile web add github:cherrchen/dsh-plugin-multi-root-workspace#v0.1.1
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 从本地源码安装(开发调试)
|
|
46
112
|
|
|
47
113
|
```sh
|
|
48
114
|
git clone https://github.com/cherrchen/dsh-plugin-multi-root-workspace.git
|
|
@@ -50,8 +116,11 @@ cd dsh-plugin-multi-root-workspace
|
|
|
50
116
|
export CI=true # 无 TTY 时 pnpm 的依赖自检会中止,见开发工作流 §8
|
|
51
117
|
pnpm install
|
|
52
118
|
pnpm build # 生成 lib/(未纳入 Git),制品测试与安装都依赖它
|
|
119
|
+
dsh plugin --profile web add "$PWD"
|
|
53
120
|
```
|
|
54
121
|
|
|
122
|
+
> **提示**:如果你的 DSH 是 clone 源码方式使用(而非 `npm install -g @deepseek-ai/deepseek-harness`),`dsh` 不在全局 PATH 里,请把上述命令中的 `dsh` 换成 `pnpm dsh`——例如 `pnpm dsh plugin --profile web add ...`、`pnpm dsh --profile web`。
|
|
123
|
+
|
|
55
124
|
## 运行方法
|
|
56
125
|
|
|
57
126
|
开发自检(每步的预期结果):
|
|
@@ -60,7 +129,7 @@ pnpm build # 生成 lib/(未纳入 Git),制品测试与安装都依
|
|
|
60
129
|
pnpm lint && pnpm typecheck # 预期:0 警告 0 错误;两个 tsconfig 全部通过
|
|
61
130
|
pnpm test # 预期:全部通过;本机没有的内核 runner 用例会显式 skip 并打印原因
|
|
62
131
|
pnpm kernel:probe # 预期:报告本机可用的内核 runner(seatbelt / bwrap / landlock)
|
|
63
|
-
pnpm smoke # 预期:compose
|
|
132
|
+
pnpm smoke # 预期:compose 40/40、behavior 99/99、journey 55/55
|
|
64
133
|
pnpm docs:check # 预期:0 errors, 0 warnings
|
|
65
134
|
```
|
|
66
135
|
|
|
@@ -71,7 +140,7 @@ dsh plugin --profile web add "$PWD"
|
|
|
71
140
|
dsh --profile web --dump-config
|
|
72
141
|
```
|
|
73
142
|
|
|
74
|
-
预期:组合里**仅** `fs-sandbox` 与 `sandbox` 两行被替换为插件的 `multi-root-fs` / `multi-root-sandbox`,并插入 scope / registry / command
|
|
143
|
+
预期:组合里**仅** `fs-sandbox` 与 `sandbox` 两行被替换为插件的 `multi-root-fs` / `multi-root-sandbox`,并插入 8 行(`multi-root-compat` / `fs` / `sandbox` / `scope` / `registry` / `instructions` / `command` 与 client 载体行 `multi-root-client`);`bash-sandbox` 保持上游(bash 与 PTY 经 `ctx.sandbox` 取根)。启动 `dsh --profile web` 后,侧栏底部出现 Folders 动作。
|
|
75
144
|
|
|
76
145
|
## 使用方法
|
|
77
146
|
|
|
@@ -104,16 +173,21 @@ Writable additional roots: 2 of 2.
|
|
|
104
173
|
src/
|
|
105
174
|
roots.ts 纯规则层:canonical 化、冲突校验、登记状态分类(available/missing/redirected/invalid)
|
|
106
175
|
registry.ts 根注册表:dsh-storage-domain 持久化,同一主根的变更在一条队列里串行
|
|
176
|
+
registry-lease.ts / registry-lease-win32.ts
|
|
177
|
+
跨进程 Registry Authority:POSIX flock 与 Windows named semaphore(进程死亡由内核释放)
|
|
107
178
|
scope.ts ctx.multiRootScope:唯一的授权源,兼发模型可见的拓扑快照
|
|
108
179
|
fs.ts 多根文件系统 provider(进程内 fence,子类自上游 LocalFileSystem)
|
|
109
180
|
sandbox.ts 多根内核沙箱 provider(子类自上游 LocalSandboxProvider)
|
|
110
181
|
dialects.ts Seatbelt / bwrap / Landlock profile 的识别与附加 grant 拼装(不认识即响亮报错)
|
|
111
182
|
containment.ts 路径包含判定(词法快速路径 + dev/ino 别名回退)
|
|
183
|
+
instructions.ts 附加根 AGENTS.md / CLAUDE.md 的发现、增量投递、预算与撤回(顶层 + 本会话触碰过的子目录,经 agent/pre-step 注入)
|
|
184
|
+
compat.ts multi-root-compat 启动门禁(版本 allowlist 与混装判定)
|
|
185
|
+
compat/ 版本差异适配层:dsh-version / sandbox-confine / agent-instructions / llm-message(可选 peer 按需加载)
|
|
112
186
|
command.ts /workspace-folders 命令与面板 RPC 的 host 半部
|
|
113
187
|
contract.ts 面板线协议(zod 双端校验,可内联进浏览器 bundle)
|
|
114
188
|
client/ 浏览器半部:侧栏动作、对话框、双语词典
|
|
115
|
-
tests/ 差分 parity、方言真实执行矩阵、契约往返、组件与 locale
|
|
116
|
-
scripts/ 冒烟(compose / behavior / journey
|
|
189
|
+
tests/ 差分 parity、方言真实执行矩阵、契约往返、组件与 locale 门禁、跨进程 lease e2e、指令注入
|
|
190
|
+
scripts/ 冒烟(compose / behavior / journey)、文档与内核 runner 检查、兼容性契约检查、升级流程
|
|
117
191
|
docs/ 需求、架构、决策记录(ADR)、计划、开发工作流
|
|
118
192
|
```
|
|
119
193
|
|
|
@@ -121,24 +195,28 @@ docs/ 需求、架构、决策记录(ADR)、计划、开发工
|
|
|
121
195
|
|
|
122
196
|
欢迎 Issue 与 PR:
|
|
123
197
|
|
|
124
|
-
1. 先读 [`AGENTS.md`](./AGENTS.md)(仓库级规则)与[开发工作流](./docs/development/plugin-development-workflow.md)
|
|
198
|
+
1. 先读 [`AGENTS.md`](./AGENTS.md)(仓库级规则)与[开发工作流](./docs/development/plugin-development-workflow.md)(环境、命令、运行时支持矩阵、升级流程)。
|
|
125
199
|
2. 从 `main` 拉分支;提交信息遵循 conventional commits(`feat` / `fix` / `perf` / `refactor` + scope),参见现有历史。
|
|
126
200
|
3. PR 前本地必须全部通过:`pnpm lint`、`pnpm typecheck`、`pnpm build`、`pnpm test`、`pnpm docs:check`、`pnpm smoke`(CI 按同序执行,先 build 后 test)。
|
|
127
201
|
4. 影响行为的变更需同批更新 `docs/` 下的对应文档;README 保持中英双语同步;有取舍的工程决策请新增 ADR。
|
|
128
202
|
5. 硬约束:**不修改上游仓库(deepseek-harness)任何包**;插件只做 out-of-tree 扩展。
|
|
129
203
|
|
|
130
|
-
##
|
|
204
|
+
## 已知限制
|
|
131
205
|
|
|
206
|
+
- 附加根的指令文件按需到达模型:根目录顶层的那一份在会话第一步之前注入,子目录里的那一份在本会话**成功**触碰过该目录之后注入;nested 文件只会在其目录被考察到时重新检查(根层每一步,子目录依赖已投递或有新触碰),投递状态是进程内的(resume 后可能再告知一次),也只识别 `read` / `write` / `edit` 三个工具名。主根与 user-global 的指令链仍由上游负责,本插件不重复注入([ADR-0010](./docs/decisions/ADR-0010-additional-root-instruction-scope.md))。
|
|
207
|
+
- 跨进程单写者:同一 `$DSH_HOME` 上同时只允许一个 DSH 进程持有根登记表;另一个进程显示登记表不可用(`registry-contended`),持锁者退出或崩溃后刷新即接管——这是刻意的 fail-closed,不是待修的竞态([ADR-0007](./docs/decisions/ADR-0007-registry-authority-lease.md))。
|
|
208
|
+
- 支持矩阵是精确版本 allowlist:宿主版本不在清单上、或核心包混装了不同版本时,`fs` / `sandbox` / `registry` / `instructions` 四行**不启动**,组合退化为"没装这个插件"([ADR-0009](./docs/decisions/ADR-0009-dsh-compat-contract.md))。
|
|
132
209
|
- Windows 的内核级多根未实现:`fs` 写路径覆盖附加根,但受限 bash/PTY 写不进去(非空 scope 时插件输出一次显式告警);详见[需求文档](./docs/requirements/multi-root-workspace.md)第一期范围。
|
|
133
210
|
- 附加根与主根同权(无 per-root read-only);附加根不能作为 bash/PTY 的默认工作目录(session cwd 语义不变)。
|
|
134
211
|
- `workspace-files`(Client 文件树)仍只看主根。
|
|
135
212
|
- 命令的输出文案为英文(host 侧没有活动语言信息),面板文案中英双语跟随界面语言。
|
|
136
|
-
- Workspace Folders 面板是「侧栏底部动作 +
|
|
213
|
+
- Workspace Folders 面板是「侧栏底部动作 + 对话框」,不是独立全屏面板:`sidebar.footer.action` 是所有受支持版本都提供的插槽,而 `sidebar.panellist`/`main` 不是。
|
|
137
214
|
|
|
138
215
|
## 文档
|
|
139
216
|
|
|
140
217
|
项目长期文档位于 [`docs/`](./docs/README.md):
|
|
141
218
|
|
|
219
|
+
- [版本变更记录(CHANGELOG)](./CHANGELOG.md)
|
|
142
220
|
- [需求](./docs/requirements/multi-root-workspace.md)
|
|
143
221
|
- [目标架构](./docs/architecture/multi-root-workspace.md)
|
|
144
222
|
- [上游调研](./docs/reference/multi-root-workspace-research.md)
|
|
@@ -149,8 +227,6 @@ docs/ 需求、架构、决策记录(ADR)、计划、开发工
|
|
|
149
227
|
|
|
150
228
|
Coding Agent 的仓库级规则定义于 [`AGENTS.md`](./AGENTS.md)。
|
|
151
229
|
|
|
152
|
-
English documentation: [`README.en.md`](./README.en.md)
|
|
153
|
-
|
|
154
230
|
## 许可证
|
|
155
231
|
|
|
156
232
|
[MIT](./LICENSE)
|