A dynamic Cordis plugin for DSH (DeepSeek Harness) that browses workspace directories and files from the left sidebar. Two entry points — the “File Explorer” pill at the top of the sidebar and the “Open Directory” button in the session header — slide out a 320px panel that locates the current workspace directory. Click a directory to enter it, click a file to preview its text content. The toolbar can open the current directory in the system file explorer with one click, and file references can be appended to the chat input while previewing.
Since v1.5.0 this is a static bundle plugin loaded automatically from the profile layer stack — install once, and it activates on every DSH startup with no manual define/run needed.
cwd); re-locates automatically when you switch workspaces, while re-opening in the same workspace keeps your last positionInvoke-Item on Windows; v1.6.1 adapts to the newer session/openWorkspacePath Typert Remote endpoint, falling back to the legacy host.openPath / workspaces.openPath when unreachable)@client/client.js), files outside keep the absolute path, and paths with spaces follow the official quoted grammar (@"path with spaces"); the input box shows only a short reference, and DSH’s native FILE_REFERENCE_PROMPT makes the model treat @-prefixed paths as explicitly referenced files to read via the read tool![]() |
![]() |
![]() |
# Install (static bundle, recommended; published to npm)
dsh plugin --profile web add fexp-file-explorer
For local development or unpublished copies use file: pointing at this repository (the path must NOT contain spaces):
dsh plugin --profile web add file:/path/to/fexp-file-explorer
Restart dsh web and the plugin activates automatically: the “File Explorer” pill appears at the top of the sidebar and the “Open Directory” button appears in the session header. No manual define/run required.
@relative-path / @"path with spaces") to the input draft, then edit and send;Invoke-Item on Windows).| Path | Description |
|---|---|
package.json + cordis.patch.yml + lib/ + client/ |
Static bundle (recommended): auto-loaded on DSH startup after dsh plugin add |
host-source.js + client-source.js |
Dynamic-plugin fallback form: for profiles without bundle support |
manifest.json |
Plugin metadata + restore parameters (plugin/name/purpose/version) |
LICENSE |
MIT License |
assets/ |
UI screenshots (screenshot-1~3.png) |
AGENTS.md |
Agent collaboration conventions (rebuild flow / change workflow / coding conventions / versioning) |
README.md / README.en.md |
中文 / English docs |
| Static bundle (v1.5.0, recommended) | Dynamic plugin (fallback) | |
|---|---|---|
| Loading | dsh plugin add installs into the profile layer stack; auto-loaded on DSH startup |
Must be re-registered with cordis_define + cordis_run after every DSH restart |
| Code | lib/index.js (Host) + client/client.js (Client) |
host-source.js + client-source.js |
| When to use | Normal profiles (web, etc.) | Profiles without bundle support |
Dynamic fallback steps:
host-source.js and client-source.js;cordis_define: plugin: { kind: "new", idPrefix: "fexp" }, name/purpose from manifest.json (purpose includes the CAPABILITIES: fs, rpc declaration), code.host/code.client as the full contents of the two source files;cordis_run to activate; success when the panel appears.The dynamic form does not survive DSH restarts and must be reloaded; the static bundle form has no such limitation.
lib/index.js and mounts three JSON routes via webServer (/fexp/default-root, /fexp/list-dir, /fexp/read-file) on top of the DSH fs service (resolve/listDir/stat/readText) and sandboxPolicy.workspaceRoot; the dynamic fallback in host-source.js exposes the same three methods as Package-private RPCs via harness.handle.shell.overlay, conversation.session.header.actions, conversation.input.dock, sidebar.footer.action) and never replaces built-in UI; plain JS + React.createElement, no JSX/TS. The static form registers via window.__ModuleLoader__.load, replaces host.call with fetches to same-origin routes, and replaces styles.insert with a self-managed <style> tag.useStore self-heals by re-reading state right after subscribing, so a probe update landing before the subscription is never lost; sidebarWide defaults to true so the top button stays visible even if the probe fails.host.openPath (Invoke-Item on Windows). Since v1.5.2 it calls the native API directly (POST /api/host.openPath, the official client-request envelope protocol, same-origin) — intercepting plugins like dsh-better-sidebar monkey-patch workspaces.openPath and reroute “open directory” into “open file in the sidebar editor”, which rejects directories with "…" is a directory; direct calls bypass the patched channel, falling back to workspaces.openPath when fetch is unavailable or the network layer fails; no new Host RPC, no spawn/external network capability.conversation.input.dock slot captures the standard inputActions (setDraft) and useInput (draft subscription); “Add to chat” calls setDraft(existing draft + reference) — the same official channel the built-in UI uses to write the input box. Pure client capability, no new Host RPC. Since v1.6.0 the inserted reference uses the DSH @ file-command grammar (mirroring the official file-reference formatFileMention): relative path inside the cwd, absolute path outside, quoted @"…" when the path contains spaces; DSH’s system-prompt FILE_REFERENCE_PROMPT makes the model treat @-prefixed paths as explicitly referenced files to read via the read tool, so no long Markdown link is needed.path === null, so after browsing once (or entering a subdirectory), closing and reopening — or switching workspaces — never reloaded, leaving the previous workspace’s directory on screen. A boundWs binding state now re-binds and reloads the current workspace directory whenever boundWs !== current wsPath on open or workspace switch; re-opening in the same workspace keeps the last position. Both entries go through openPanelFor; the toolbar “current workspace” goes through bindWorkspace.#e8edf3 text) that vanished on light themes; they now use CSS classes .fexp-entry-btn / .fexp-entry-btn-active with theme CSS variables (label-primary text, bg-layer-1/2 background, border-l2 border, brand-primary active state), adapting automatically to any theme with hover feedback.fill="currentColor", no stroke), default size 16px; the Lucide line icons from v1.3.0 are superseded.TopToggle now watches the search button’s aria-expanded attribute with a MutationObserver (the expanded search button carries aria-expanded="true" and its next sibling is an input[type=text], which distinguishes it from the session rows / group-collapse buttons that also use aria-expanded); the entry button hides while the search box is expanded and reappears when it collapses. Host unchanged.CAPABILITIES: fs, rpc — filesystem access (the host fs service) and regular RPC only; no network requests, no spawn/process, no credential access.plugin_security_review / plugin_security_audit (see dsh-plugin-security-review).FS_TOO_LARGE error and should be read by the assistant in conversation. The static bundle loads with the profile; the dynamic fallback form does not survive DSH restarts and must be rebuilt.| Version | Notes |
|---|---|
| v1.6.1 | Fixed “Open in system Explorer” breaking after a DSH upgrade: newer DSH (0.1.3+) refactored the host open-path RPC away from host.openPath into the Typert Remote endpoint session/openWorkspacePath (the official client calls ctx.remote.session.openWorkspacePath({ path }), actually POST /api/session/openWorkspacePath with the payload wrapped as { args: { request } }); the old endpoint no longer exists (404), and the fallback workspaces.openPath was also removed from the newer client, so the button stopped opening the folder. nativeOpenPath now tries the new endpoint first and throws on an explicit business error; it only falls back to the old endpoint and to workspaces.openPath (when still present) when the new one is unreachable (404/network/non-JSON). Host unchanged; security review WARN (38/300) |
| v1.6.0 | “Add to chat” now inserts a DSH @ file-command reference instead of the long [file name](absolute path) text: official grammar @relative-path (inside cwd) / @absolute-path (outside) / @"path with spaces" (when containing spaces); DSH’s native FILE_REFERENCE_PROMPT makes the model treat @-prefixed paths as explicitly referenced files to read via the read tool; multiple additions separate by spaces; the input box shows only a short reference; pure client capability, security review stays WARN (38/300) |
| v1.5.3 | Trimmed the “Open in system Explorer” button tooltip: the 14-char 在系统资源管理器中打开当前目录 becomes the 6-char 打开资源管理器, matching the 4–6 char style of the other toolbar buttons; wording-only change, no logic change; security review stays WARN (38/300) |
| v1.5.2 | Fixed “Open in system Explorer” being hijacked by intercepting plugins: with dsh-better-sidebar installed (it monkey-patches workspaces.openPath by default, interceptOpenPath: true), the call was rerouted to “open the file in the sidebar editor”, which rejects directories with "<path>" is a directory. openInExplorer now calls the native DSH host.openPath directly (the same client-request envelope protocol as the official client, same-origin POST /api/host.openPath, no new Host RPC / no external network), bypassing the patched channel; falls back to workspaces.openPath when fetch is unavailable or the network layer fails, and reports business errors inside the envelope as-is; the toolbar button no longer depends on workspaces being present. Host unchanged; security review WARN (38/300) |
| v1.5.1 | Fixed the “File Explorer” button covering the “Search sessions” box: expanding the search box from the workspace title row placed the fixed-positioned entry button right on top of the input. TopToggle now uses a MutationObserver on the search button’s aria-expanded state (expanded search button = aria-expanded="true" + next sibling input[type=text], distinguishable from session-row/group-collapse buttons), hiding the entry button while the search box is expanded and restoring it on collapse; Host unchanged, pure client capability, security review stays WARN (33/300) |
| v1.5.0 | Static-bundled: package.json (dsh.bundle.patch + dsh.client.platform) + cordis.patch.yml + lib/index.js (three JSON routes on webServer) + client/client.js (__ModuleLoader__ registration, host.call→fetch, styles→self-managed tags); auto-loads from the profile layer stack, no manual define/run after restarts; dynamic sources kept as fallback; security review stays WARN (33/300) |
| v1.4.0 | Icons switched to Google Material Icons (Apache 2.0): solid fill stays crisp at small sizes, Chrome/Android-grade; close panel=keyboard_double_arrow_left («, like VS Code), workspace=workspaces, root=home, up=arrow_upward, refresh=refresh, open-in-explorer=folder_open, close preview=close; svgIcon uses fill=currentColor, default size 14→16px; Host unchanged |
| v1.3.0 | Icons switched to Lucide (ISC): panel-left-close, briefcase, house, folder-up, refresh-cw, folder-open, x (superseded by Material Icons in v1.4.0); Host unchanged |
| v1.2.3 | Fixed entry-button text invisible on light themes: switched to CSS classes with theme CSS variables (label-primary text / bg-layer-1/2 background / brand-primary active), adaptive to any theme with hover feedback; Host unchanged |
| v1.2.2 | Fixed “File Explorer” opening the previous workspace’s directory: the path===null guard in the panel effect never reloaded after switching workspaces; added boundWs workspace binding with auto re-location; both entries go through openPanelFor; Host unchanged |
| v1.2.1 | Removed the “added” state: the “Add to chat” button stays always enabled for adding multiple files |
| v1.2.0 | Added “Add to chat” in the preview header: hidden bridge in conversation.input.dock captures inputActions/useInput, appends the file reference to the input draft; pure client capability |
| v1.1.0 | Added “open in system file explorer” toolbar button via DSH native host.openPath (Invoke-Item on Windows); no new Host RPC/capability |
| v1.0.0 | Final shape: top “File Explorer” + header “Open Directory” dual entries; dark-theme inline styles; startup race fix (self-healing subscription + wide-sidebar default) |
| (pkg-1~pkg-7) | Evolution: sidebar bottom button → top overlay → pill tab → inline-style visibility fixes → removed bottom button and diagnostics bar |
feat: feature / fix: bug fix / docs: docs / refactor: refactor / chore: chores); source / manifest / README stay in sync.AGENTS.md before making changes; rebuild and change workflows are documented there.vX.Y.Z) and gh release create after each feature iteration; the docs site builds automatically on GitHub Pages (https://shanhaifish.github.io/fexp-file-explorer/).