DeepSeek Harness Plugin

chengzhi43/dsh-file

Stars ★ 1 Category UI Enhancements Added 2026-08-18

VS Code-style file manager: browse the conversation workspace in a sidebar tree and edit files in the center column with Monaco, with theme import/export and Markdown preview.

Install

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:chengzhi43/dsh-file

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).

README


dsh-file

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 侧边栏浏览当前对话工作区的文件,在中间主区域编辑。

Screenshot

Browse the workspace in the sidebar tree; clicking a file opens it in the center-column "Files" view (Monaco editor with syntax highlighting).

Installation

# Run from inside the cloned dsh-file directory (not its parent)
cd /path/to/dsh-file
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).

DSH Desktop install

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-file directory as well
cd /path/to/dsh-file
dsh plugin --profile desktop add .

After installing, fully quit and relaunch the desktop app (quit the application, not just close the window); dsh-file will then show up in the sidebar footer "Files" button 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 is dsh.profile.bundles + dependencies in the profile's package.json (which is exactly what dsh plugin add does).

Install from npm

dsh plugin --profile web add dsh-file

Or download the tarball from Releases and install it locally (use --profile desktop for the desktop app):

dsh plugin --profile web add ./dsh-file-0.1.1.tgz

Configuration

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-file
      name: 'dsh-file'
      config:
        root: !!js process.cwd()   # fallback root only, before the file manager pins the session workspace

Features

  • "Files" button at the sidebar footer: toggles the sidebar body into the file manager (file tree) and back to the workspace/session list
  • Workspace follows the active conversation: opening the file manager resolves the current session's workspace directory (SessionHeader.cwd) and re-pins the gateway root via setRoot — no longer the directory dsh web was launched from
  • Center-column editor (view tab): the editor is registered as a conversation.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
  • Markdown preview: .md files open as a read-only rendered preview by default (marked + GFM: headings, lists, tables, task lists, code blocks). A VS Code-style preview/source toggle button sits next to the "Theme" button in the toolbar (shown only for Markdown files); the chosen mode is remembered (localStorage) and reused on the next open
  • Theme settings (VS Code style): the "Theme" button in the editor toolbar opens a settings panel — light by default, presets selected via a dropdown (Light/Dark/One Dark/GitHub), plus custom background / foreground colors and font size (10–28px), applied live to Monaco and the editor chrome (toolbar/status/tabs follow the background), persisted to localStorage
  • Theme import/export: export the current theme to a JSON file and import it back, just like VS Code, to migrate your colors between environments (see Theme import/export)
  • Edit & save: Ctrl+S or the "Save" button in the editor, dirty marker (●); multiple open files switch via the top tab strip, each tab has a ✕ close button
  • File operations: create file, create directory, rename, delete (delete requires confirmation; non-empty directories are rejected)
  • Workspace boundary: every path resolves against the currently pinned root; escaping paths are rejected by the host (including symlink-escape protection)

Theme import/export

The 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.

Export a theme

  1. Open the file editor (the "Files" view in the center column).
  2. Click the Theme button in the toolbar to open the settings panel.
  3. Click Export theme — the browser downloads a dsh-file-theme-YYYY-MM-DD.json file.

The exported JSON carries both the plugin's flat fields and VS Code workbench colors:

{
  "name": "dsh-file · One Dark",
  "type": "dsh-file-theme",
  "version": 1,
  "background": "#282c34",
  "foreground": "#abb2bf",
  "fontSize": 13,
  "colors": {
    "editor.background": "#282c34",
    "editor.foreground": "#abb2bf"
  }
}

Import a theme

  1. Open the theme settings panel.
  2. Click Import theme and pick a JSON file.

Accepted formats:

  • This plugin's export format (background / foreground / fontSize);
  • VS Code theme JSON: reads 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.

Architecture

The plugin has two halves sharing the package name dsh-file:

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()

Host ↔ Client communication (Typert Remote)

Browsers cannot touch the filesystem directly, so the host half exposes file operations as RPC endpoints (namespace fileManager: listDir / readText / writeText / createFile / createDirectory / rename / 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.

Panel toggle mechanism

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.

Dependency resolution (important)

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:

  • On startup the desktop app re-points ~/.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).
  • The stripped types break 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.
  • Do not run 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.

Development

npm install                       # esbuild + typescript + types
node build.mjs                    # build host (tsc) + client bundle (esbuild)
node build.mjs --watch            # watch client only (rerun for host changes)

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)

Debugging

dsh --profile web --dump-config | grep -A4 dsh-file   # 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":{}}}'

FAQ

  • RPC returns not found: almost always the @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.
  • Blank editor: Monaco loads from the jsdelivr CDN; in intranet environments configure a local mirror or wait for the textarea fallback.
  • Wrong directory opened: verify the current session's workspace directory (the sidebar title shows the directory name). The file manager auto-runs setRoot to the current session's cwd; without a session it falls back to cordis.patch.yml's root.
  • Plugin changes have no effect: host-half changes require restarting dsh web; client-half bundle changes only need a page refresh (a rev change triggers a reload).

License

MIT

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →