dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
左侧边栏的“资料浏览器”和主工作区的“文本编辑器”,页签方式展示,适合讨厌工作区被过度分割的人。
GitHub · Installation · Theme import/export · FAQ · Issues · Releases
English · 简体中文
A VS Code-style file manager plugin for DeepSeek Harness Web: browse the current conversation's workspace from the sidebar and edit files in the center column.
DeepSeek Harness 的 VS Code 风格文件管理器插件:在 Web 侧边栏浏览当前对话工作区的文件,在中间主区域编辑。
Browse the workspace in the sidebar tree; clicking a file opens it in the center-column "Files" view (Monaco editor with syntax highlighting).
# Run from inside the cloned dsh-explorer-editor directory (not its parent)
cd /path/to/dsh-explorer-editor
dsh plugin --profile web add .
dsh plugin add pnpm-links the package into the profile and appends it to dsh.profile.bundles. Restart dsh web to take effect (client plugin metadata is cached by name; it is rescanned after a restart).
The desktop client is the deepseek-harness-desktop project (package dsh-plugin-desktop). It uses a separate profile (desktop) from dsh web (web), and plugins are not shared between them — a plugin installed only into the web profile will not appear in the desktop client, nor in its Settings → Plugins list:
# Run from inside the dsh-explorer-editor directory as well
cd /path/to/dsh-explorer-editor
dsh plugin --profile desktop add .
After installing, fully quit and relaunch the desktop app (quit the application, not just close the window); dsh-explorer-editor will then show up in the sidebar "Workspace / Files" tabs and in Settings → Plugins.
Note: do not add plugins to
~/.dsh/profiles/desktop/cordis.yml— the desktop client rewrites it to an empty list[]on every startup. The correct entry point isdsh.profile.bundles+dependenciesin the profile'spackage.json(which is exactly whatdsh plugin adddoes).
dsh plugin --profile web add dsh-explorer-editor
Or download the tarball from Releases and install it locally (use --profile desktop for the desktop app):
dsh plugin --profile web add ./dsh-explorer-editor-0.11.1.tgz
The root in cordis.patch.yml is only the fallback root when there is no session (defaults to process.cwd()). When the file manager opens, the browser resolves the current conversation's workspace directory and re-pins the root via setRoot, so usually nothing needs to change:
- insert:
- id: dsh-explorer-editor
name: 'dsh-explorer-editor'
config:
root: !!js process.cwd() # fallback root only, before the file manager pins the session workspace
allowArbitraryRoot: true # whether setRoot may escape root (see below)
Security boundary:
allowArbitraryRootdefaults totrue, sosetRootfollows the current conversation's workspace (which may live outsideroot). To restrictsetRootto directories underroot(preventing the RPC from reading files outside the workspace), set it tofalse. All file operations still respect the workspace boundary — any path escaping the currently pinned root is rejected (including symlink escapes).
SessionHeader.cwd) and re-pins the gateway root via setRoot — no longer the directory dsh web was launched fromconversation.view view ("Files" tab, alongside Chat/Trajectory). Clicking a file opens it inside the session scroll area of the page (not a popup): Monaco Editor (the same kernel VS Code uses, loaded from CDN) with extension-based syntax highlighting; falls back to a plain textarea when the CDN is unreachable.md files open in source mode (editable) by default; a VS Code-style preview/source toggle button sits next to the "Theme" button in the toolbar (shown only for Markdown files) to switch to a read-only rendered preview (marked + GFM: headings, lists, tables, task lists, code blocks). The chosen mode is remembered (localStorage) and reused on the next openfs.watch and pushes filesystem changes to the browser over SSE — the tree reloads only the affected directories (VS Code Explorer-like). Refocusing the window triggers a full backstop refreshroot; escaping paths are rejected by the host (including symlink-escape protection)dshFile namespace, zh/en) and follows the DSH language — no more mixed Chinese/EnglishThe theme panel (the "Theme" button in the editor toolbar) can export the current theme to a JSON file or import one back — the same idea as VS Code theme files, handy for moving your colors across machines or environments.
dsh-explorer-editor-theme-YYYY-MM-DD.json file.The exported JSON carries both the plugin's flat fields and VS Code workbench colors:
{
"name": "dsh-explorer-editor · One Dark",
"type": "dsh-explorer-editor-theme",
"version": 1,
"background": "#282c34",
"foreground": "#abb2bf",
"fontSize": 13,
"colors": {
"editor.background": "#282c34",
"editor.foreground": "#abb2bf"
}
}
Accepted formats:
background / foreground / fontSize);colors["editor.background"] and colors["editor.foreground"] (tokenColors are not applied yet — syntax highlighting keeps Monaco's built-in colors).On success the colors apply immediately and are persisted to localStorage; invalid JSON or missing valid colors shows an error in the panel.
The plugin has two halves sharing the package name dsh-explorer-editor:
| Host half (Node process) | Client half (browser React) | |
|---|---|---|
| Source | src/index.ts | src/client/ |
| Build output | dist/index.js (tsc, keeps standard decorators) | dist/client.js (esbuild, ModuleLoader bundle) |
| Responsibility | Filesystem RPC | Sidebar file tree + center-column editor view |
| Key API | class FileManagerGateway extends TypertRemoteService + @Remote() | ctx.slots.register(), ctx.remote.$mount() |
Browsers cannot touch the filesystem directly, so the host half exposes file operations as RPC endpoints (namespace fileManager: listDir / readText / readDataUrl / writeText / createFile / createDirectory / rename / copy / delete / stat / resolve / getRoot / setRoot). The client mounts the call surface with ctx.remote.$mount(TYPERT_REMOTE) and resolves the service via ctx.get('remote.fileManager'). setRoot re-pins the gateway root to the current session's workspace directory.
Key constraint (SRC descriptor contract): the Typert gateway derives wire parameter names from method signatures via Function.prototype.toString — host methods must use flat parameters (listDir(path: string), not listDir(input: {...})); the parameter names are the wire fields the client sends. Both halves must use identical names.
The sidebar main area is the single-seat sidebar.workspaces slot (occupied by the workspace browser at priority 0). The plugin registers its own shadow entry at priority: -1 when the button is clicked — a single-seat slot renders the lowest-priority live entry, so the file manager wins the cell; closing disposes the entry and the workspace browser returns. After clicking a file in the tree, the editor renders in the "Files" view registered in conversation.view — the session scroll area of the center column (alongside chat / trajectory), entered via the "Files" tab in the session header, never a popup.
Base-package patch: the sidebar.workspaces.tabs slot must be hosted by the workspace browser bundle (@deepseek-ai/dsh-client-ui-workspace/lib/client.js) — declare the sub-slot in the children of the sidebar.workspaces registration, inject the slot's subscribe/getSnapshot in browserInjected, and render the slot in place of sectionLabel in the WorkspaceBrowser header using useSyncExternalStore to check hasTabs. Upgrading dsh overwrites that file, so the patch must be re-applied after an upgrade (local backups live under dsh-tools/backup/ as dsh-client-ui-workspace.client.js.bak-*). Without the patch the plugin falls back to the sidebar-footer "Files" button.
The @deepseek-ai/* packages must not be installed as copies inside the plugin's own node_modules: @Remote decorator markers live in a module-level WeakMap, and if the plugin and the api-gateway each hold a separate dsh-typert-protocol instance the markers are invisible to each other (RPC returns 404). Node must resolve to the same instance as the dsh installation:
# Local development (when dsh is installed locally via npx):
ln -s ~/.dsh/profiles/node_modules/@deepseek-ai node_modules/@deepseek-ai
At startup dsh maintains a flat symlink fallback at $DSH_HOME/profiles/node_modules (healProfilesModuleFallback) pointing at every package in the dsh installation. For production releases the plugin declares @deepseek-ai/* as peerDependencies, provided by the profile.
Desktop app (deepseek-harness-desktop) notes:
~/.dsh/profiles/node_modules/@deepseek-ai at the packaged Desktop.app directory (/Applications/DSH Desktop.app/.../app.asar.unpacked/node_modules), which strips .d.ts files — keeping the symlink above pointing at profiles guarantees the plugin loads the same runtime instance as the desktop api-gateway (RPC works).tsc. tsconfig.json uses paths to map the compile-time lookup of @deepseek-ai/* to the global dsh install (which ships full .d.ts); runtime resolution is unaffected (Node still walks the node_modules symlink → profiles → desktop instance). Adjust the path per the comment in tsconfig.json if your global dsh lives elsewhere.npm install inside the plugin directory: npm dereferences the node_modules/@deepseek-ai symlink into a real directory and corrupts the profiles symlink structure, making dsh fail with "exists and is not a symlink". If you must add dependencies, re-run the ln -s above afterwards.npm install # esbuild + typescript + types
node build.mjs # build host (tsc) + client bundle (esbuild)
node build.mjs --watch # watch host (tsc --watch) + client (esbuild)
Build outputs:
dist/index.js — host half (Node ESM; compiled with tsc to keep the standard stage-3 decorators; esbuild would lower @Remote to the legacy form and crash at runtime)dist/client.js — client half (window.__ModuleLoader__.load({id, factory}) format; react and other seed words stay external)dsh --profile web --dump-config | grep -A4 dsh-explorer-editor # confirm the plugin layer is composed
# Test RPC (requires a running dsh web)
curl -X POST http://127.0.0.1:3080/api/fileManager/getRoot \
-H 'Content-Type: application/json' \
-d '{"type":"client-request","rpcId":"t","method":"fileManager/getRoot","payload":{"args":{}}}'
@deepseek-ai/dsh-typert-protocol dual-instance problem — check whether the plugin's node_modules/@deepseek-ai is a symlink (ls -la node_modules/@deepseek-ai); if not, create the link as described above and restart.dsh-explorer-editor:monaco-mirror to use a private mirror). If all mirrors are unreachable it falls back to a plain textarea.setRoot to the current session's cwd; without a session it falls back to cordis.patch.yml's root.dsh web; client-half bundle changes only need a page refresh (a rev change triggers a reload).