AngelosZou/dsh-multi-folder
Secondary working directories for a DSH project: the agent keeps the primary workspace as cwd and gains equal read/write/exec permissions on configured secondary directories, configurable from the session header and the new-session page.
Listed
5
Tools
Bundle verified
Preview
What it does
Secondary working directories for a DSH project: the agent keeps the primary workspace as cwd and gains equal read/write/exec permissions on configured secondary directories, configurable from the session header and the new-session page.
Best for
- Projects whose source, tests, and documentation live in separate directories but must be edited within one DSH session.
- Users who want to preserve the primary workspace as the agent cwd while granting equivalent permissions to selected secondary roots.
- Teams that want secondary directories configured before session creation or adjusted from the session header without adding agent tools.
Not ideal for
- Commands that must write to the primary and a secondary directory, or two secondary directories, in one invocation; each confined command has one writable root.
- Workflows that rely on `git -C <secondary>` or absolute-path writes while the command cwd remains in the primary workspace; file-creating commands must use the target root as `workdir`.
- DSH profiles not composed from `@deepseek-ai/dsh-base` and `@deepseek-ai/dsh-web-app`, or environments below Node.js 20.
- Users who only work in one directory and do not need additional workspace roots.
README
dsh-multi-folder
| English | 中文 |
Secondary working directories for a DeepSeek Harness project — edit a source repo, a test repo, and a docs repo side by side without leaving the primary workspace.
A DeepSeek Harness plugin bundle that gives one project (workspace) a set of secondary working directories:
- The agent’s core
cwdand every other core attribute keep pointing at the primary workspace. - Under Workspace Write mode the agent gains the same read / write / edit / execute permissions on the configured secondary directories as on the primary workspace — enforced by re-rooting the session’s own sandbox policy, so every mode keeps its semantics (
read-onlystill denies,workspace-writeallows,danger-full-accessallows). - The directory list is injected into the system prompt and re-rendered per session assembly.
- Configuration changes notify the agent through a non-interrupting message queue — delivered at the next message boundary (user send or tool-call end), and only when the directory set actually changed.
- Configurable before the session starts: the session-creation page (new-session screen) offers a Multi-folder entry that reads and edits the same per-workspace configuration through a sessionless remote API (
multiFolder/*endpoints) — no session id required. - No new tools. Everything is a framework-level change (tool-pipeline interception) plus a UI-level change (a session-scoped header entry).
Requirements
- Node.js >= 20
- A DSH profile composed from
@deepseek-ai/dsh-base+@deepseek-ai/dsh-web-app
Install
Link this repository into a DSH profile:
dsh plugin --profile web add dsh-multi-folder
Then restart the DSH backend (host composition loads at process start) and refresh the browser page (the client bundle is served no-cache).
Usage
A Multi-folder button appears in the session header, and a second entry appears on the session-creation page (fixed launcher in the bottom-right corner while the new-session screen is shown; an inline chip beside the workspace picker once the upstream conversation.hero.workspaceExtras slot is available). The panel lets you:
| Action | Behavior |
|---|---|
| Add directory | Opens the native directory picker |
| Remove / refresh | Applies immediately |
| Switch session | The panel auto-switches to that session’s directories |
| Reopen panel | Uses the per-session cache — no redundant command rows |
Equivalent slash command for the user:
/multi-folder list
/multi-folder add "D:\path\to\repo"
/multi-folder remove "D:\path\to\repo"
/multi-folder set "D:\a" "D:\b"
The agent needs nothing extra: read / glob / grep work everywhere, and write / edit / pwsh / bash are intercepted and re-rooted automatically when the target path (or workdir) falls inside a configured secondary directory.
Permission model
Each confined command runs under exactly ONE writable root — the workspace root the call is re-rooted to (the Windows ACL runner grants a single workspace write SID per process tree). Consequences:
- A command whose cwd stays the primary workspace cannot create files inside a secondary directory.
git -C <secondary> commit,cd <secondary>inside a script,git clone <url> <secondary>, or absolute-path writes all fail with an OS-levelPermission denied(e.g.fatal: Unable to create '.../.git/index.lock': Permission denied). - Symmetrically, a command re-rooted to a secondary directory cannot write to the primary workspace (or another secondary directory) in the same invocation.
-
Rule for file-creating commands: set
workdirto the directory the command writes into. For git, run the command from inside the repository (passworkdirpointing at it) instead of usinggit -Cfrom the primary workspace. - Reads are unrestricted and need no
workdir.
When a shell run ends in such a denial and references a configured secondary directory, the plugin attaches a short diagnostic hint to the tool result explaining the workdir fix.
How it works
-
Interception — a listener on the
tools/executearound-dispatch waterfall short-circuitswrite/edit/pwsh/bashcalls whose resolved path (orworkdir) lands inside a configured secondary directory, and executes them with the session’s standing sandbox policy re-rooted to that directory ({ ...standingPolicy, workspaceRoot: secondaryDir }). The mode itself is untouched, which is what gives every sandbox mode its identical primary-workspace semantics for free. Paths are canonicalized throughfs.resolve+processPathbefore matching, so.., symlinks, and case differences behave correctly. -
Prompt injection — one ordered
systemPromptsection with a text provider evaluated per assembly, rendering only for sessions whose workspace has configured directories. -
Notifications — a pending notice armed by the command handler (only on actual change) is consumed at the next boundary by either the
agent/pre-stepwaterfall (prepend into the entering message batch) or thetools/post-executewaterfall (attach asadditionalContexts), whichever fires first — the framework’s native plugin-sourcednoticecontext. -
Configuration & security boundary — per-workspace config lives in a host-owned store outside every agent sandbox root (
<DSH_HOME>/storages/multi-folder/<workspace-key>.json). Directwrite/editattempts against the config file are rejected with an explicit message — the agent can never self-grant directories; configuration is user-managed by design. See SECURITY.md. -
Sessionless remote API — a
multiFoldernamespace registered throughctx.typert.register(hand-writtensrc-jsondescriptors) plus a plain-object service provided asmultiFolder. Itslist/add/remove/setmethods are keyed by workspace path and share one validated core with the/multi-foldercommand, so the creation page can configure directories before any session exists. -
Client — a hand-maintained factory bundle (
window.__ModuleLoader__.load), no build toolchain required. The panel drives the host through two channels: the Remote BFF (ctx.remote.commands.execute) for sessions, and the shared/apiRPC channel (ctx.connection.rpc.call) for the sessionless endpoints.
Project layout
| Path | Purpose |
|---|---|
cordis.patch.yml |
Profile patch layer inserting the dsh-multi-folder row |
lib/index.js |
Host plugin: config store, tool-pipeline interception, prompt injection, dual-channel notifications, /multi-folder command, sessionless multiFolder/* remote API |
lib/client.js |
Client plugin (factory bundle): session-header button + overlay panel + session-creation page entry (hero launcher / upstream hero chip) |
test/ |
Runtime-free behavior tests (see Development) |
docs/ |
Design and analysis documents |
Development
No build step: the host half is plain ESM and lib/client.js is a hand-maintained factory bundle in the DSH client-modules format. Tests run with Node directly:
node test/smoke-host.mjs # host apply smoke test + remote API behavior
node test/intercept.mjs # interception / command / notification behavior
node test/smoke-client.mjs # client bundle + panel flows (React shim)
Before modifying lib/client.js, see docs/design.md for the bundle contract.
Documentation
- docs/design.md — architecture and security model
-
docs/upstream-hero-slot.md — the upstream
conversation.hero.workspaceExtrasslot change (B1) and its plugin-side consumption
Contributing
See CONTRIBUTING.md. Issues and pull requests are welcome.
License
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:AngelosZou/dsh-multi-folder in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.