- Início
- Plugins
- Segurança e permissões
- dsh-multi-folder-workspace
dsh-multi-folder-workspace
boy-grid/dsh-multi-folder-workspace
Multi-folder workspace for DeepSeek Harness: pick several folders and create one workspace spanning them (directory-flow occupant + extended workspace/sandbox contract)
Instalar
dsh plugin --profile web add github:boy-grid/dsh-multi-folder-workspaceREADME
DSH Multi-Folder Workspace
npx @boy_grid/dsh-mfw web
Multi-folder workspaces for DeepSeek Harness (DSH): one workspace made of several folders that live in different places. Sessions inside it can read and write every member folder, the workspace sits in the sidebar next to ordinary single-folder ones, and members can be added or removed later.
Why this is a two-track repository
Upstream DSH models a workspace as exactly one folder, and that assumption is spread across its core packages — the persisted record (path: string), the sandbox policy (workspaceRoot: string), the RPC payloads, and the directory-picker contract (onPicked(path)). A plugin cannot rewrite the types and executors of services that already exist in the process, so this feature cannot be a plugin alone.
Upstream also does not accept external pull requests right now. So this repository ships two things that must be installed together:
| Track | Deliverable | What it does |
|---|---|---|
| Core fork | Patch set — 10 packages / 27 patches, applied through pnpm patchedDependencies | Multi-path domain model, multi-root sandbox, extended RPCs, onPicked(paths[]) contract, model-visible membership trail |
| Plugin | @boy_grid/multi-folder-workspace | The multi-select directory flow and the member-management UI |
The plugin depends on contracts that only exist after the patches are applied. It will not work on a stock DSH install.
patchedDependenciesis the supported route because it covers every instance in the tree, including peer edges — pnpmoverridesdoes not apply topeerDependencies, which is why the@boy_grid/dsh-*republish packages thatscripts/repack.mjscan produce stay experimental and are not published.
Installing both tracks by hand is a handful of steps, so a third package does it for you: @boy_grid/dsh-mfw is a launcher that provisions the patched runtime, keeps its own DSH profile with the plugin installed, and then forwards your arguments to dsh. See packages/dsh-mfw.
Compatibility
| Version | |
|---|---|
| DSH baseline | 0.1.1-rc.2 — pinned exactly; see fork/fork.json |
Upstream latest at time of writing | 0.1.1-rc.2 |
| Node | >= 22 |
| pnpm | 11.7 (or corepack) |
The baseline tracks the npm latest dist-tag. When upstream publishes a new release the patch set has to be rebased before it works there — see docs/RELEASE.md §6 for that procedure. Pin @deepseek-ai/dsh exactly and keep a lockfile: upstream packages reference each other with ^<baseVersion> ranges that would otherwise drift to a newer prerelease and leave the patches unused.
Security note
A multi-folder workspace widens the agent's write surface. In workspace-write mode, a session in the workspace can write to every member folder, not just its own working directory. Concretely:
- The patch set modifies DSH's sandbox components:
dsh-sandbox(writableRootsbecomes the union of all members),dsh-sandbox-policy(resolves the member set per session through a newctx.workspaceRootsseam),dsh-sandbox-local(Seatbelt / bwrap / Landlock multi-directory grants), anddsh-sandbox-windows-acl(one Write ACE per member directory). - Paths outside the member set are still denied, and the
FS_SANDBOX_DENIED/[sandbox: …]escalation path is unchanged.read-onlyanddanger-full-accesssemantics are untouched. - Member changes take effect immediately: adding a member grants write access to existing sessions right away, removing one revokes it right away.
- The member set and every add/remove is projected into the model's context and appended as a
workspace/membershipsession event, so the trail is auditable and replayable.
Review that trade-off before installing. If you only ever work in one folder at a time, you do not need this.
Quick start
npx @boy_grid/dsh-mfw web
The launcher provisions a patched DSH runtime in your cache directory (first run only, roughly 10–30 seconds), installs the plugin into a DSH profile of its own, and boots the web UI. Everything after web goes to dsh unchanged, so npx @boy_grid/dsh-mfw web --port 8080 works as you would expect.
It stays out of the way of an existing DSH install: your web profile and its cordis.patch.yml are never read or written, so plain dsh web keeps behaving exactly as before, while sessions and credentials in $DSH_HOME are shared by both. npx @boy_grid/dsh-mfw doctor prints every path it resolved, and clean --all removes the cache again. Details in packages/dsh-mfw/README.md.
Requirements: Node >= 22, network on the first run, and pnpm 11+ — taken from PATH when it is new enough, otherwise fetched through corepack.
Manual install (advanced)
Prefer to own the install tree — pinning it in your own repository, or composing it with other patches? The repository ships a ready-made kit with the patches, the manifest and a frozen lockfile:
cp -R consumer-template /path/to/my-dsh-deploy
cd /path/to/my-dsh-deploy
pnpm install --frozen-lockfile --config.confirmModulesPurge=false
pnpm add @boy_grid/multi-folder-workspace
Then put the plugin and the browse backend into the profile's cordis.patch.yml, and disable the native picker:
- id: directory-picker
disabled: true
- insert:
- id: directory-picker-browse
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: multi-folder-workspace
name: '@boy_grid/multi-folder-workspace'
That file only exists after a profile has been created, which happens the first time dsh web runs — so start it once, stop it, then edit. Restart dsh web afterwards. A successful install has 10 directories with a _patch_hash suffix under node_modules/.pnpm. Full steps are in docs/RELEASE.md.
Repository layout
dsh-multi-folder-workspace/
├── plugins/multi-folder-workspace/ the published plugin (client half: multi-select flow + member UI)
├── packages/dsh-mfw/ the published launcher (npx entry point; carries the patch set)
├── fork/
│ ├── fork.json manifest: scope / forkVersion / baseVersion / package list
│ └── patches/<pkg>/ per-package step patches (NN-*.patch) + NOTES.md
├── consumer-template/ copy-and-go consumer kit: manifest + patches + frozen lockfile
├── scripts/
│ ├── combine-patches.mjs replay step patches into one normalized patch per package
│ ├── repack.mjs experimental @boy_grid/dsh-* republish pipeline
│ ├── build-client.mjs plugin client half -> __ModuleLoader__ format
│ ├── deploy.sh / run-experiment.sh / serve.sh / wire-plugin.sh / toggle-plugin.sh
│ ├── test-setup.sh / integration-smoke.mjs cross-package integration smoke
│ ├── gen-consumer.sh / make-test-bundle.sh consumer kit + cross-platform test bundle
│ ├── test-platform.sh / test-platform.ps1 bootstrap scripts shipped in the test bundle
│ └── lib/pnpm.sh shared pnpm resolution (.tool -> PATH -> corepack)
└── docs/ design, status, verification and release docs (Chinese)
Development
pnpm install
pnpm typecheck # tsc --noEmit against the pinned baseline
pnpm build:plugin # host half + client half in __ModuleLoader__ format
pnpm patches # dist/fork/combined/<pkg>.patch (10 files)
pnpm test # unpacks + patches the baseline, provisions an upstream tree,
# then runs the cross-package smoke test (11 checks)
pnpm consumer:regen # rebuild consumer-template and prove pnpm can apply all 10 patches
pnpm experiment # deploy + wire the plugin + start dsh web on an isolated DSH_HOME
The launcher carries its own copy of the combined patch set, so after pnpm patches it needs a resync:
pnpm launcher:assets # copy dist/fork/combined into packages/dsh-mfw/assets + refresh its manifest
pnpm launcher:verify # fail if those assets drifted (also runs on prepack and in CI)
To try the launcher against a local plugin build without publishing anything, point it at the checkout and give it a throwaway cache and home:
DSH_MFW_PLUGIN=$PWD/plugins/multi-folder-workspace \
DSH_MFW_CACHE=/tmp/mfw-cache DSH_HOME=/tmp/mfw-home \
node packages/dsh-mfw/lib/bin.js web
The experiment deployment lives outside the repository (default $HOME/.cache/dsh-mfw-experiment, override with MFW_DEPLOY_DIR) because it holds a real DSH_HOME with credentials. MFW_DSH_HOME overrides the home separately, and PORT the web port.
One pnpm gotcha worth knowing: any scripted pnpm install needs --config.confirmModulesPurge=false, otherwise pnpm can stop at an interactive "modules directories will be removed and reinstalled" prompt and hang. The repository scripts already pass it.
Known limitations
- No type declarations for the extended contracts. The patches only touch the runtime
lib/*.js.workspace.paths,addMember/removeMember,onPicked(paths[])andSandboxExecutionPolicy.workspaceRootsexist at runtime but not in the.d.tsfiles, so TypeScript consumers of those extension points need their owndeclare moduleaugmentation. - The baseline is pinned. A newer DSH release needs a rebase of the patch set first.
- Two UI polish items are missing: a member-count badge on the workspace row, and choosing which member folder a new session starts in (it currently defaults to the primary member).
- Upstream features are not multi-root aware unless they go through the sandbox seam. For example the
@file reference added in0.1.1resolves against the session's own working directory, not across member folders. - Nested members are rejected by design: no member may be an ancestor or a descendant of another member, in the same workspace or across workspaces.
- Uninstalling is safe but one-way. Records written by the patched side keep
pathset to the primary member, so a stock DSH still starts and shows those workspaces as single-folder ones. But stock DSH strips thepathsfield the first time it writes a record, so the member set is gone after that. Back up$DSH_HOME/storages/workspace.jsonif you want to keep it. - On Windows, uninstalling leaves ACEs on the member folders. Write access is granted by adding a capability-SID ACE to each member directory's DACL, which lives in the file system rather than in
DSH_HOME, so removing the cache and the profile does not take it with them. The ACE grants nothing once no process carries the matching SID, but restoring the original ACLs takes one manual pass — command indocs/platform-testing.md.
Documentation
The docs are written in Chinese.
| Document | Contents |
|---|---|
| packages/dsh-mfw/README.md | The launcher: commands, flags, environment, what it does and does not touch (English) |
| docs/architecture.md | Upstream constraints, design decisions per layer, fork baseline strategy, customization seams |
| docs/implementation-status.md | What is implemented per patch, with verification evidence, and the remaining boundaries |
| docs/RELEASE.md | Release runbook, consumer install, and the rebase procedure for new upstream releases |
| docs/manual-e2e-checklist.md | Manual GUI checklist |
| docs/platform-testing.md | Per-platform sandbox coverage and the cross-platform test bundle |
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md. The most useful contributions are rebases onto new upstream releases and platform-specific sandbox verification.
License
MIT. The patch set is derived from the MIT-licensed upstream @deepseek-ai/dsh-* npm artifacts; the upstream copyright notice is preserved in THIRD_PARTY_NOTICES.md.
This is an independent community project. It is not affiliated with or endorsed by DeepSeek.
Plugins relacionados
api-relay-audit
toby-bridges/api-relay-audit
dsh-auto-review
perrylink/dsh-auto-review
dsh-claude-ux
eri64/dsh-claude-ux
dsh-remote
xgone/dsh-remote