dsh-workspace-combiner
ninglovegithub/dsh-workspace-combiner
DSH 工作区组合器:左侧边栏管理多工作空间,每个绑定一个主目录(工作区锚点/文档区)+ 若干代码项目;新建向导自动识别 Java/Vue/React/Python/Go 等项目并支持多选,新建会话在主目录打开,把所有项目绝对路径注入 system prompt,联动 @chaoset/sandbox-extra-roots 放开读写白名单。
インストール
dsh plugin --profile web add github:ninglovegithub/dsh-workspace-combinerREADME
dsh-workspace-combiner
Built for full-stack developers — bundle a frontend repo, a backend repo, and a docs anchor into one AI session. Absolute paths injected, sandbox writable roots auto-synced, API changes edited on both sides in the same conversation.
中文 · English
A Cordis dual-face plugin for DSH (DeepSeek Harness) that turns a set of repositories into a coordinated multi-workspace joint development context.
The plugin registers a Workspace Combiner tab in the left sidebar. Each workspace
bundles a primary directory (the "workspace anchor" — where docs and non-code assets
live) plus an ordered list of code-project directories (for example a backend repo and
a frontend repo). When you start a new session, the plugin injects the absolute paths of
all active directories into the system prompt and syncs the writable ones into the
@chaoset/sandbox-extra-roots whitelist.
To keep that injected context from exploding, the plugin applies a four-layer context-control model:
- Directory layer — per-directory access (
readwrite/readonly/disabled), primary anchoring, drag ordering. - Config layer —
anchor/singleworkspace mode plus optional group tags. - Load-mode layer —
full/summary/treecontrolling how much file detail is injected. - Command layer —
@-command dynamic scope.
Layers 1-4 ship today; see Changelog · 中文.
Features
- Workspaces — create / rename / delete / switch named workspaces; each owns a primary directory plus ordered code-project directories with per-directory access.
- New-workspace wizard — pick a name + base path; the host creates the primary folder and a scan detects Java / Vue / React / Python / Go projects for multi-select.
- Three ways to add a directory — select from native DSH workspaces, open the host directory picker, or paste an absolute path.
- Directory tri-state access —
readwrite(sandbox-writable),readonly(visible but not writable),disabled(excluded from the prompt). The primary directory is alwaysreadwrite. - Workspace mode —
anchor(primary = docs anchor) vssingle(primary = core business code). - Directory grouping — optional group tags (docs / backend / frontend / reference / other) rendered as sub-headers.
- Snapshots — save / restore / delete a workspace's directory configuration; restore re-syncs the sandbox immediately.
- File index + load modes — gitignore-aware bounded file-tree scan with an mtime cache; a per-workspace load mode controls injected detail.
- Code/API index — deterministic HTTP-endpoint extraction that links the server
registration site to the client call site as
file:lineacross TS/JS/TSX/JSX/Vue and Java/Kotlin/Go/Python/Ruby/C#. Toggleable, budgeted, and copyable as@featureName. - Deterministic endpoint correlation — each endpoint records which directory each side
lives in;
/api/user/detailand/user/detailjoin up (one optional/api, optionally followed by/vN), and Spring's class-level@RequestMapping("/user")is composed with method mappings into/user/list. No semantic guessing, so no false positives. - Endpoints touched by these changes — a deterministic reverse lookup from the git working tree, through the file→endpoint map built during the same scan, to the endpoints those files declare or call, with the counterpart file on the other side.
- Per-project command handbook —
run/test/buildper directory, edited inline and injected with absolute paths so the model knows which command to run where. Prefilled from the detected project type only where the command is certain. - Context monitor (estimates) — a budget modal with per-directory file counts and estimated
tokens, so you can tune the load mode and watch context shrink. Fixed overhead is derived as
whole-block render − each block, so the figures add up to what is actually injected. - Real token usage (measured) — the same modal shows provider-reported usage: uncached input / cache read / cache write / output, cache hit rate, an occupancy bar against the context window, and the plugin's share of the latest request.
- @-command dynamic scope — a prompt section teaches the model to resolve
@-prefixed tokens as explicitly referenced paths across all workspace roots (@dir/,@file,@"path with spaces"), pulling files into scope on demand. - Sandbox sync — read-write directories are pushed into
sandbox-extra-rootsextraWritableRoots(hot reload with a file-write fallback); the bottom legend shows how many directories are actually in the allowlist. - Git status per directory — each row shows its branch, amber with
*Nwhen there are uncommitted or untracked changes, grey when clean, plus↑Nwhen ahead of the upstream. Directories that are not repositories show nothing. - Directory notes — attach a free-form note to any directory, edited inline; it travels with the directory configuration.
- Project-directory annotations — the primary row is badged
Docs onlyand the othersCode, so the anchor-vs-source distinction is visible at a glance (hover for an explanation). - Live injected-prompt preview — expand a read-only block that renders exactly what will be injected for the current configuration, and copy it.
- Context budget — an editable per-workspace token budget with a donut summary, 2x2 stat cards, and a per-directory column chart; the panel warns past 80% and past 100%.
- Fixed-height panel with local scrolling — the panel fills the sidebar and only its lists scroll, so the header and the New-session button never leave the screen.
- Compact entry cards + modals — advanced config, coding standards, prompt preview and the context budget are left-column entry cards that open modals, so they no longer occupy permanent panel height.
- Keyboard —
Cmd/Ctrl+Nnew session,Cmd/Ctrl+Kcommand palette,Escto close; the palette covers switching workspaces, adding directories, refreshing stats and changing modes.
Architecture
dsh-workspace-combiner/
├── package.json # dsh field: bundle.patch + client.inject
├── cordis.patch.yml # registration patch
├── tsconfig.json # typecheck
├── tsdown.config.ts # dual-entry build: host + client
└── src/
├── invariant.ts # shared constants (plugin id / API paths / prompt order)
├── core/types.ts # shared types (WorkspaceRef / Workspace / StoreShape / ...)
├── prompt.ts # multi-workspace prompt + file-index rendering
├── store.ts # host persistence (~/.dsh/dsh-workspace-combiner.json)
├── sandbox-sync.ts # sandbox-extra-roots writable-root sync
├── routes.ts # /api/dsh-workspace-combiner routes (loopback-only)
├── host/
│ ├── index.ts # host entry: prompt section + session/created + routes
│ ├── projectDetector.ts # scan a directory for project type
│ ├── fileIndex.ts # gitignore-aware bounded file-tree scanner + mtime cache
│ ├── gitStatus.ts # branch / dirty / untracked / ahead per directory
│ └── contextStats.ts # token estimator + per-directory context stats
└── client/
├── index.ts # client entry: sidebar icon + main-column panel
├── types.ts # client-local mirrored types
├── locales.ts # zh/en dictionary + tt() helper
├── api.ts # client -> host fetch API
└── panel/
├── WorkspaceCombinerPanel.tsx # panel body + icon
├── controller.ts # state management
├── NewWorkspaceWizard.tsx # create-workspace wizard
├── typeBadge.tsx # project-type badge
├── naming.ts # name sanitize / dedupe
└── styles.ts # injected <style> (theme-aware)
Data flow
[client panel] --POST /api/.../...--> [host store]
│
session/created (top-level new session)
▼
selectionBySession: sessionId -> { directories, mode, loadMode, fileTrees }
▼
systemPrompt.section(text fn renders per session)
▼
injects "# Multi-workspace joint development mode active" + directory list + file index
On directory changes the host also calls sandbox-extra-roots
sandboxExtraRootsConfig.set({ extraWritableRoots }) for hot reload; if the remote is
unavailable it falls back to an atomic write of
~/.dsh/plugins/sandbox-extra-roots/config.json.
Install (local directory)
Prerequisite: DSH is installed and the dependency plugin is present.
# 0) dependency plugin (required)
dsh plugin --profile desktop add @chaoset/sandbox-extra-roots
# 1) build lib/ (host + client)
cd /path/to/dsh-workspace-combiner
pnpm install
pnpm build
# 2) load the plugin
dsh plugin --profile desktop add file:./dsh-workspace-combiner
# 3) verify
dsh plugin --profile desktop ls dsh-workspace-combiner
# 4) restart DSH Desktop (or re-run "dsh web" for the web profile)
Update after changing source: pnpm build, then remove and re-add the plugin.
Usage
- Open the Workspace Combiner tab in the sidebar.
- Pick a workspace from the list, or create one with the New workspace wizard (name + base path; the host creates the primary folder and scans for code projects).
- Add directories (folder picker or a pasted absolute path) and review each row: the primary row is marked Docs only, the rest Code, with its Git branch, group and access. Reorder by dragging; select several rows to bulk-edit access, group or delete.
- Open Advanced for the workspace mode, the file load mode and snapshots, and the @ command cheat sheet.
- Expand Injected prompt to preview exactly what will be sent, and copy it if useful.
- Click New session — the primary directory is opened, a session is created, the active directories are injected into the system prompt and the sandbox writable roots are synced.
- Watch the Context budget card: donut, stat cards and the per-directory column chart. Drag the divider above it to trade space with the directory list.
⚠️ Directory changes only affect newly created sessions; already-open sessions are not re-loaded.
Injected prompt (appended to the system prompt)
# Multi-workspace joint development mode active
Current session loads[2]project directories:
1.example-anchor[Primary: Workspace anchor (docs/non-code area)]absolute path: /abs/path
[Backend]
2.example-backend[Code project]absolute path: /abs/path
# File index (load mode: summary)
[example-anchor]/abs/path
12 files / 3 dirs
[example-backend]/abs/path
210 files / 42 dirs
# Per-project commands (must be run in the matching absolute path; for self-start and self-verification)
- example-backend (/abs/path): run `mvn spring-boot:run` | test `mvn test`
# Code/API index (auto-extracted, for navigation; the real code wins)
- @workspaceCreate → /api/dsh-workspace-combiner/workspace-create | server src/routes.ts:130 | client src/client/api.ts:39
# @-command dynamic scope
- Tokens prefixed with @ are explicitly referenced paths: @absolute/path or @relative/to-a-workspace-root
- A trailing slash marks a directory: list its tree when its contents matter
- Otherwise it is a file: read it first, never claim inspection before reading
- @"path with spaces" quotes a path containing spaces
- @-referenced files/dirs take priority; paths outside the file index are still readable (sandbox reads are unrestricted)
Development rules:
1. Read/write files and view code must use full absolute paths — no relative paths
2. Different repositories' Git commits are independent and do not interfere
3. On API changes, update backend and frontend request code together
4. Terminal commands must be run with full absolute paths, not relative paths
Configuration (optional)
No schema; configure via cordis.patch.yml:
workspace-combiner:
enabled: true # master switch
announceToAgent: true # inject the multi-workspace prompt section
Design notes / constraints
- The session shell has a single, immutable cwd. The plugin does not try to change it; instead the prompt rules force absolute paths.
- New sessions only. On
session/createdthe selection is snapshotted and bound to that session id; old sessions and child/fork sessions (those withparentSession) are not injected. - Read model — reads are already unrestricted in the DSH sandbox; the tri-state
access only gates writes (via
extraWritableRoots) and prompt inclusion. - Dependencies are declared in
package.jsondsh.client.injectandpeerDependencies(@chaoset/sandbox-extra-roots, DSH host services). - Aux routes (loopback-only):
scan,file-index,git-status,context-stats,workspace-patch, plus state / workspace CRUD routes.
Development
pnpm install
pnpm typecheck # tsc --noEmit
pnpm build # tsdown -> lib/host/index.js + lib/client.js
pnpm watch # watch rebuild
- Host logs:
ctx.logger.warn(...)on sync failures. - Persistence:
~/.dsh/dsh-workspace-combiner.json. - Sandbox config:
~/.dsh/plugins/sandbox-extra-roots/config.json.
License
MIT