WeKnora
Tencent
Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.
navid-kianfar/dsh-add-assets
Options plate on the DeepSeek Harness composer's + button — pick files and folders from the project or anywhere on the machine, upload from your device, or run a slash command — with inline path chips and a Claude Code-style attachment preview.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:navid-kianfar/dsh-add-assets
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
A plugin for the DeepSeek Harness (dsh) Web Client. It replaces the composer's + button with a menu for adding files and folders, uploading images, or opening the slash-command menu. Files and folders come from a picker that can browse the project or the whole Host machine. Picked paths go into the draft as inline reference chips. Pending attachments show as cards, and images open in a full-size preview.

+ menuThe menu sits at the start of the composer's tool row. While replaceCommandButton is on, the harness's own + is hidden, so the row keeps one +.
| Row | What it does | Default shortcut (macOS / other) |
|---|---|---|
| Add files | Opens the picker in files mode. | ⌘U / Ctrl+U |
| Add folders | Opens the picker in folders mode: only directories are listed, and they can be selected. | ⇧⌘U / Ctrl+Shift+U |
| Upload from this device | Opens the browser's file chooser for PNG, JPEG, WebP, and GIF images. The composer validates them the same way it validates paste and drop. Shown only while deviceUpload is on. |
none |
| Slash command | Opens the composer's own slash-command menu. | ⌘/ / Ctrl+/ |
If neither picker scope is available, the two picker rows are disabled with a note. The same happens to the command row when the harness has no trigger pipeline (@deepseek-ai/dsh-client-ui-input-trigger). The menu is disabled while a message is being submitted.

When both scopes are available, tabs at the top of the picker switch between them:
@deepseek-ai/dsh-file-reference-local in the stock web profile). Typing a name at the root searches the whole workspace. Typing a path containing / lists that directory. Paths are workspace-relative.addAssets.browse endpoint. It opens at the Host account's home directory and shows one level at a time, with directories first. The breadcrumbs run from the filesystem root, and the home directory's crumb is labelled Home. Paths are absolute. The tab is offered only while outsideWorkspace is on.In either scope, you can type a path into the search field to list that directory. Whatever follows the last separator filters the list. In This machine, /tmp/x lists /tmp filtered by x, and ~/Down lists your home directory filtered by Down. On Windows, only drive-letter paths (C:\Users\) and ~\ are accepted.

Your selection is kept when you change directories, filter, or switch tabs. You can collect files from both scopes and add them all in one step. Show hidden reveals dot-prefixed entries.
| Key (in the search field) | Action |
|---|---|
↑ / ↓ |
Move the highlight |
Enter |
Files mode: open the highlighted folder, or tick the highlighted file. Folders mode: tick the highlighted folder. |
→ (caret at end of field) |
Open the highlighted folder |
← (caret at start of field) |
Go up one level |
Cmd+Enter or Ctrl+Enter |
Add everything selected |
Esc |
Close without adding |
During IME composition, Enter, the arrow keys, and Esc are left to the input method.

Each picked path becomes an inline reference chip. The chip shows the basename, with a trailing / for a folder. Its hidden value is the @path mention that the composer's own @ completion would write. Paths containing whitespace are quoted, as in @"My Files/a.txt". When the message is sent, the model receives that mention text. Copying a chip copies it too.
If the harness has no trigger pipeline, there is nothing to turn chips into text on send. In that case the plugin appends the plain @path text to the draft instead.
A path containing a double quote or a control character cannot be written as a mention. Such picks are skipped, and a notice under the + says how many were skipped.

The plugin takes over the composer's attachment row (conversation.input.attachments):
Image cards show a thumbnail, the file name, and a details line with format, pixel size, and byte size. Click a card to open the full-size preview. In the preview, ← and → (or the side buttons) move between images, the trash button removes the current image, and Esc, the close button, or a click on the dimmed backdrop closes it.

File cards are for non-image attachments the composer accepts. While a file uploads, the card says so. If the upload fails, click the card to retry.
Every card has a remove button.
Dragging files over the page shows a drop target. The plugin handles the drop, because the shipped attachment row it replaces used to. If the composer cannot accept files at that moment, the target says so.

The Add assets card on Settings → Plugins edits every configuration key. Edits are held until you click Save, and Discard drops them. Save stays disabled while a shortcut or number field is invalid. If the harness reports the section as not writable, the card is read-only.
dsh web). The plugin was built and tested against this release. Its composer integration depends on details of that release, listed under Known limitations.^22.19 || >=24 (engines in package.json).PATH, which dsh plugin uses to install packages.The plugin declares @deepseek-ai/cordis, @deepseek-ai/dsh-file-reference, @deepseek-ai/schemastery, and @deepseek-ai/dsh-typert-protocol as peerDependencies. All four ship with the harness. Its browser half asks the harness to inject dsh-api-remotes, dsh-client-locale, dsh-client-ui-conversation, dsh-client-ui-input-trigger, dsh-client-ui-settings, and dsh-client-ui-settings-plugins (the dsh.client.inject field in package.json).
These harness services are optional, and the plugin works without them:
| Harness service | Without it |
|---|---|
File-reference provider (remote.fileReferences) |
No Project tab. The picker offers This machine only. |
Input trigger pipeline (inputTriggers) |
The Slash command row is disabled, and picks are written as plain @path text instead of chips. |
Settings provider (settings) |
The profile's values are used as they are, and the settings card cannot change them. |
Operating systems. POSIX hosts use absolute paths. Windows hosts accept only drive-letter paths: UNC (\\server\share) and drive-less (\foo) paths are refused. The Windows path rules are covered by unit tests only and have not been tried on a live Windows host.
dsh plugin --profile <name> runs the rest of the command as a pnpm command in that profile's directory. After a successful install, it adds any package that declares dsh.bundle to the profile's bundle list:
dsh plugin --profile web add @achasoft/dsh-add-assets
A packed tarball installs the same way:
dsh plugin --profile web add ./achasoft-dsh-add-assets-0.1.0.tgz
Restart dsh web afterwards. To confirm the plugin loaded, dump the composed configuration and look for its layer, # == @achasoft/dsh-add-assets:
dsh --profile web --dump-config
To uninstall:
dsh plugin --profile web remove @achasoft/dsh-add-assets
This also removes the package from the profile's bundle list. If your profile's cordis.patch.yml still has a patch targeting add-assets, the harness warns patch: entry "add-assets" not found and skips it. Delete that patch as well.
The package's cordis.patch.yml becomes one layer of the profile. It inserts two rows:
Row id |
name |
Role |
|---|---|---|
add-assets |
@achasoft/dsh-add-assets/host |
Host half: the settings section and the addAssets.browse endpoint. Carries the configuration. |
add-assets-ui |
@achasoft/dsh-add-assets |
Browser half. Must stay the bare package name, because the Web Client finds a plugin's browser half by resolving <name>/package.json. |
Layers apply in this order: bundle patches in bundle-list order, then $DSH_HOME/profiles/<name>/cordis.patch.yml, then $DSH_HOME/cordis.patch.yml, then any --patch files. To override the configuration, target the row by id in a later layer. A patch replaces the row's whole config, and the schema requires every key, so list all ten:
- id: add-assets
config:
replaceCommandButton: true
deviceUpload: true
outsideWorkspace: false
browseMaxEntries: 500
filesShortcut: mod+u
foldersShortcut: mod+shift+u
commandShortcut: alt+/
pickerResultLimit: 20
previewDensity: compact
previewDetails: true
The patch file is a top-level list of patches, as above. Do not wrap it in a patch: key: the harness rejects that with patch: id is required for non-insert patches.
Values from the profile are only starting values. Anything saved from the settings card is stored by the harness settings provider and takes precedence over them.
The schema defines no defaults, and a row missing any key fails at load. The defaults below are the values in the shipped cordis.patch.yml. The settings card can edit every key.
| Key | Default | Allowed | Meaning |
|---|---|---|---|
replaceCommandButton |
true |
boolean | Hide the harness's own +. With false, both buttons show: the harness's + still opens the slash-command menu, and this plugin's + sits beside it. |
deviceUpload |
true |
boolean | Show the Upload from this device row. Paste and drop work either way. |
outsideWorkspace |
true |
boolean | Offer the This machine tab. The endpoint checks this value on every call. See Security. |
browseMaxEntries |
500 |
integer 1–2000 | Most entries the Host returns for one directory level. |
filesShortcut |
mod+u |
shortcut or "" |
Opens the picker in files mode. |
foldersShortcut |
mod+shift+u |
shortcut or "" |
Opens the picker in folders mode. |
commandShortcut |
mod+/ |
shortcut or "" |
Opens the slash-command menu. |
pickerResultLimit |
20 |
integer 1–200 | Rows the picker shows per query. In Project scope the provider applies its own cap, maxResults (default 20), so raise that setting as well. |
previewDensity |
card |
card, compact |
card shows a thumbnail, name, and details line. compact shows a small thumbnail and name, with no details line. |
previewDetails |
true |
boolean | Show the details line in card density. Upload progress and failures are shown regardless. |
+-separated, case-insensitive list of modifiers followed by exactly one key.mod, cmd, command, ctrl, and control all mean the platform accelerator: Command on Apple devices and Ctrl elsewhere. alt and option mean Alt. shift means Shift.enter, escape/esc, space, tab, backspace, delete, up, down, left, or right.mod or alt. Shift alone does not count. Shortcuts listen on the whole page, so a plain-key shortcut would take that key away from every text field."" turns the shortcut off.mod shortcut from matching.The Host half registers one Typert Remote endpoint. The browser half mounts it as ctx.remote.addAssets and calls it through the harness gateway on the /api route. The wire format is the harness's own. This endpoint is not meant as a standalone HTTP API.
addAssets.browse(path: string, query: string) → AssetBrowseResult
| Parameter | Meaning |
|---|---|
path |
Directory to list: "" or ~ for the Host home, ~/…, or a fully qualified path. |
query |
Case-insensitive substring filter on entry names, applied before the entry cap. |
On success the result is { ok: true, listing }. listing contains:
path: the directory listed.home: the Host account's home directory.crumbs: { name, path } for each directory from the filesystem root down to path.entries: { name, path, kind: 'file' | 'directory', hidden }, directories first, each group sorted by name.truncated: whether the cap cut the level.Failures are also returned as values, { ok: false, code, message }, with these codes:
code |
When |
|---|---|
disabled |
outsideWorkspace is false |
not-a-directory |
The path names something other than a directory |
unreadable |
The path is not fully qualified, does not exist, or cannot be read |
The contract lives in generated/, and the types are exported from @achasoft/dsh-add-assets/host.
/api route, which rejects a request with 403 when its Host header is not trusted (loopback, a LAN IP address, or a configured trustedHosts entry) and with 401 when it lacks the browser session cookie. The cookie is issued from the launch token.@path text, not the file. Whether the agent can then read that path depends on the agent's own tools and permissions.outsideWorkspace is a preference, not a security boundary. Anyone using the Web Client can turn it back on from the settings card. The design assumes that person is the authenticated operator, whose agent can already run shell commands on the Host. If some browser users must not see the machine's directory tree, do not install this plugin for them, and do not give them a shell-capable agent either. Setting false in the profile will not stop them.browseMaxEntries is capped at 2000 by the schema, so a write from the browser cannot raise it. A directory is read as a stream into a sorted window of at most that many entries, so a huge directory costs the Host only the window. At most 8 symlinks are resolved at a time. On Windows, UNC paths are refused, so the Host never connects to a network server named by the browser." or a control character cannot be written as a mention.~ in the search field to go back.maxResults (default 20) without saying it did so, so the picker cannot report the level as truncated. The provider also hides dot entries unless the typed name starts with ., and it skips excluded directories, so Show hidden reveals nothing there.+ is hidden, not replaced. The harness offers no slot for that button. The plugin finds it inside the composer card by button[aria-haspopup="listbox"] and marks it hidden. If a future harness changes that markup, both buttons show.-1. Another plugin registering there at a lower priority would replace this plugin's row, including its drop handling.aria-modal and restores focus when it closes, but Tab can reach the page behind it.Development needs a sibling ../deepseek-harness checkout, because the link: devDependencies in package.json point into it. The build output (lib/, types/, tsbuild/) is not committed.
| Command | What it does |
|---|---|
npm run build |
tsc -p tsconfig.build.json, then tsdown (writes lib/host.js, lib/client.js, and the other entries) |
npm test |
Checks that generated/ matches src/host/ (check:typert), then runs vitest run |
npm run check:typert |
The Typert freshness check on its own |
npm run typecheck |
tsc --noEmit |
Run npm run build before npm test. tests/client-bundle.spec.ts renders the built lib/client.js and fails, naming the fix, when that file is missing or older than src/:
npm run build && npm test
generated/ holds the Typert contract for addAssets.browse. Only the harness's generator can produce it, so it is committed. After changing the endpoint or any type it uses, regenerate it:
node scripts/regen-typert.mjs ../deepseek-harness
The script edits files in the harness checkout and restores them from git when it finishes. Never run two regenerations at the same time.
MIT. See LICENSE.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: attachments、file-browser、file-picker、filesystem。