dsh-file-path
A DeepSeek Harness (dsh) plugin that lets the Web composer accept files and folders the built-in attachment path does not support — PDFs, archives, Office documents, large files, whole directories — by inserting their absolute paths as plain text instead of uploading them as message attachments.
This is a community plugin. It is not an official DeepSeek plugin and is not affiliated with or endorsed by DeepSeek.
Why
dsh's version-one attachment path accepts PNG, JPEG, WebP, and GIF only. Dropping a PDF into the composer currently announces "only images are supported" and drops the file. Browsers do not expose the absolute host path of a dragged File for security reasons, so a web page cannot read the path out of the drag event — even when the page is served by a local CLI.
dsh-file-path recovers the original path using the local dsh host itself:
1. You drag or paste a non-image file into the composer. 2. The browser half computes the file's SHA-256 and calls the local filePathBridge/resolvePath RPC on the dsh process. 3. The host half searches the session workspace and configured roots (~ by default) for a same-name, same-size, same-hash file and returns its original absolute path. Nothing is copied or written. 4. The composer inserts that absolute path directly. If no original is found (for example the file lives on another drive outside the search roots), a dialog asks for the absolute path manually, with an explicit "Copy to workspace" fallback available as a second action — never automatic.
A paperclip button in the composer tool row also opens a dialog for manually typing an absolute or workspace-relative path.
Install
Pack and install the built tarball:
npm run build
npm pack
dsh plugin --profile web add ./dsh-file-path-0.1.x.tgzOr install a published version:
dsh plugin --profile web add dsh-file-pathOr install from git (the prepare script builds the artifacts; pnpm ≥10 asks you to allow that build once, and dsh prints the exact allowBuilds snippet):
dsh plugin --profile web add github:<you>/dsh-file-path#<commit-sha>Then boot dsh web as usual:
dsh --profile web --port 3080> The Web profile already contains the Web app bundle. For a custom profile, > make sure @deepseek-ai/dsh-web-app is in dsh.profile.bundles before > dsh-file-path.
Usage
- Drag or paste a non-image file anywhere on the page. The plugin looks up
its original absolute path by name + size + head/tail SHA-256 samples (64KB each) and inserts that path directly. Large files are never read in full, so an 800MB archive resolves in milliseconds.
- Drag a folder (Chromium/Edge File System Access): the plugin walks the
dropped directory, builds a directory fingerprint, and inserts the matched absolute folder path. Nothing is copied.
- If the original path is outside the search roots, a dialog asks for the
absolute path manually. A secondary "Copy to workspace" action is available for unresolved FILES, but it is never triggered automatically.
- Click the paperclip button in the composer tool row to insert a manually
typed path (absolute or relative to the session workspace).
- Supported images (PNG/JPEG/WebP/GIF) keep the normal dsh image behavior. A
mixed drop routes images to the default composer intake and resolves only the rest.
- The plugin clears dsh's full-page drop overlay when it takes over a drop, so
the "drag images here" screen never remains stuck.
Configuration
The bundle patch inserts one host row. Override its config in the profile's cordis.patch.yml or any later layer:
- id: file-path
config:
importSubdir: .dsh-files # fallback copy location
maxFileBytes: 104857600 # decoded bytes per fallback import
searchRoots: # original-path search roots
- '~'
# - /mnt
searchMaxEntries: 50000 # directory entries visited per lookup
searchSkipNames: # directory basenames skipped
- .git
- node_modules
- .npm
- .cache
- .dsh
- .Trash
maxResolveBytes: 4294967296 # max file size for hash lookupsearchRootsentries must be absolute or start with~. The session
workspace is always searched first. Symlinks are not followed.
importSubdirmust be one relative segment (no/,\,..). Invalid
values fail the plugin load loudly.
- Lookup caps are per call:
searchMaxEntriesbounds traversal and
maxResolveBytes bounds the hashed candidate size.
How it is built
- Host half (
lib/index.js): a self-contained Cordis service registered as
ctx.filePathBridge. It registers strict Typert Remote descriptors for filePathBridge/resolvePath, filePathBridge/importFile, and filePathBridge/describe, so wire input is schema-validated on both sides. resolvePath walks the search roots for a content-identical file and returns its absolute path. importFile is the explicit fallback: it writes the decoded base64 payload below importSubdir with a temporary-file-plus-rename publication path.
- Browser half (
lib/client.js): adsh.clientmodule that mounts the
three Remote methods, registers locale dictionaries, and contributes a small control to the conversation.input.right slot. Capture-phase document listeners take over drops and file-only pastes that contain a non-image; all-image batches pass through untouched, images inside a mixed batch are re-dispatched to the default composer listener, and a synthetic dragend resets the default drop overlay.
Security notes
resolvePathonly reports the path of a file whose exact bytes the browser
already sent as the lookup digest; it never writes and never returns a path that does not match the dropped content.
- The explicit fallback import only writes into the live session's own
workspace, under the configured single-segment importSubdir; path traversal through the file name is stripped.
- The dsh Web transport's loopback/browser-trust fence still applies to
/api/filePathBridge/*.
- Installing a plugin means executing its code inside the local dsh process.
Review the source and pin versions just as you would for any plugin.
Development
Requires Node >=22.19.
npm install
npm run check # typecheck + tests + build
npm test # host service and composer control tests
npm pack # built tarballThe build script (scripts/build.mjs) emits the ESM host half and the window.__ModuleLoader__.load CJS-factory browser half; tsc emits the declaration files under lib/types.
License
[MIT](./LICENSE)