New:Socket for Asana Is Now Available.Learn more
Get Started

dsh-plugin-session-resources

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dsh-plugin-session-resources

DeepSeek Harness out-of-tree plugin: a file panel beside the conversation — the session's workspace as a tree, the files it produced pinned above, and a mark on every row it touched.

latest
Source
npmnpm
Version
0.2.0
Version published
Maintainers
1
Created
Source

dsh-plugin-session-resources

npm license DeepSeek Harness

English | 中文

A file panel beside the conversation. The session's workspace as a tree you can walk, the files this session produced pinned above it, and a dot on every row the session touched. Click a file to open it; the conversation makes room rather than being covered.

dsh plugin --profile web add -w dsh-plugin-session-resources

Install

-w is required. A profile directory is a pnpm workspace root, so without it pnpm refuses with ERR_PNPM_ADDING_TO_ROOT and installs nothing.

Each profile installs separately. The panel appears only in profiles that carry the package, so a desktop shell needs its own install:

dsh plugin --profile desktop add -w dsh-plugin-session-resources

No build permission is requested. The browser half is hand-written plain JS loaded through the app's own module loader, so there is no build step and no prepare script — pnpm never asks you to allowlist this package's code.

There is nothing to configure.

To update later:

dsh plugin --profile web update dsh-plugin-session-resources

What the panel shows

A button beside Session log opens it. Three things share the surface:

Produced here is pinned at the top: the files this session wrote. The tree below contains them too — they are on disk like everything else — so this repeats them on purpose. A directory listing answers "what is here"; it cannot answer "what did this conversation just make", and that is the question a reader has the moment a turn finishes. Hunting for the answer among two hundred sibling files is the work this removes.

The workspace tree is the session's own project directory, one level fetched when you first open a folder and then kept, so collapsing and reopening costs nothing. Directories sort before files. A large directory is capped and says so rather than pretending a partial listing is complete.

A dot marks a row this session touched: filled for a file it produced, hollow for one it only read. Which tools account for it is in the dot's tooltip — worth keeping, and not worth the width it took when it sat beside the filename as text.

The filter box narrows to matching names across the folders you have opened. It reaches what has been loaded and no further, which is honest: an unopened folder's contents are not on this side to search, and walking the whole disk on every keystroke would be a different feature.

Where the listing comes from

The app's own host.listDirectory cannot serve this. It backs the directory picker, where a file is not a destination, so it drops every non-directory entry on purpose — and nothing else in the RPC map reports file names.

So the host half answers on a channel of its own, registered through connection.rpc.handle, which is the supported way to add one: it puts the channel behind the same request fence the /api route uses and ties the registration to this plugin's fiber. The listing itself is fs.listDir, the one directory read in the app that reports files as well as folders.

The listing is fenced twice. The channel is loopback-only, so only a page on this machine can call it at all. Inside that, every path is resolved against the asking session's own workspace root and checked with fs.contains, so .. and an absolute path elsewhere are both refused. For an attached session the root is read from the session store rather than accepted from the caller; for a session that is merely open in history — which the store does not answer for — it comes from the caller, and the containment check is what carries the weight.

Nothing reaches a model. No tool, no prompt section, no request. The panel cannot be queried from a conversation.

When there is no listing

Some deployments cannot serve one: a composition without the filesystem service, or a shell that does not forward this channel. The panel says so in one line and falls back to two lists that need nothing from the Host — the files this session produced and the files it used as sources, folded from tool activity already in the session log.

That fallback is the whole of what this plugin used to be, and it is still what drives the dots and the pinned section, so it is a real surface rather than an apology.

How a call becomes an entry

The vocabulary is render intent, not tool names: a mutation is a diff card or a generic card whose kind is edit (the shape str_replace_editor's insert presents), and a read is a read card or a generic card whose kind is read. Reading intent rather than names is what lets a file tool nobody has written yet join this ledger by declaring what it does — no allowlist here needs updating.

tool/call starts the fold and tool/result completes it. That direction is forced rather than chosen: a mutation's file identity can live only in the call view, because a generic kind: 'edit' card names its files in call-time locations and its result view names none. The result is nevertheless what decides whether the contribution counts — a call that has not settled wrote nothing yet, and a failed call must not claim a file even when its tool already presented a call-time diff. Both failure signals are honoured: the internal failure identity (data.error, the tool threw) and the model-facing flag (isError, the tool reported failure).

A path both roles claim counts as produced: a file the session wrote is a deliverable whether or not it was also read on the way there.

Every field below card is untrusted. The wire schema locks only the for discriminant and the presence of a card-tagged object, so a version mismatch or an anomalous plugin can deliver a diff card with no paths or a locations entry with no path. The classifier narrows structurally and drops what it cannot read, so a malformed view costs one missing mark instead of a broken panel.

Opening a file, and revealing a folder

Clicking a row hands the path to the Host through the same route the tool rows use. An open that fails stays silent: the native app raises its own dialog when a path is unusable, and a row in a file list is not the place to report it.

The folder button on a row — and the one in the header, for the workspace root — appears only when the Host reports it can reach a desktop and the page is the local one. A browser on another machine must not be able to make the Host's desktop open windows, which is the same condition the app puts on its own produced-files shortcut.

Layout

The panel is a region to the right of the conversation body, starting under the session header. Dragging its edge resizes it between 240 and 720 pixels, arrow keys included.

The session header never moves. What gives up width is the scrolling body under it, which is the part a reader is looking at; the title, the utility row and the conversation tabs keep their full width. The messages re-centre themselves in what is left, because they were already held by automatic margins.

Colours, the left edge, the resize pill and the quiet scrollbar are the app's own tokens and patterns rather than choices made here, so light and dark follow the host with no theme awareness in this package.

Icons

Nine multicolour badges and twelve Lucide outlines, badge first and outline as the fallback, plus twenty-two per-language code badges composed from the same page geometry. Sources, the family table and the generator that produces the constants are in assets/file-icons/.

Known limits

  • The ledger covers the loaded history window. A call whose tool/call has scrolled out of the window leaves a pending context and contributes nothing until an older page supplies its start, so a long session's earliest marks are missing until you page back. The tree is unaffected — it reads disk.
  • The tree does not watch for changes. It is fetched when you open a folder and kept until you reload, which the header button does. A file watcher for a side panel would be a lot of machinery for a question a button answers.
  • Discovery and web results are excluded from the ledger. glob/grep paths and web_search sources are real provenance, but one discovery call can name hundreds of paths nobody opened.
  • Deletes and moves contribute no marks. A deleted file is not a deliverable, and a move's destination is outside the proven mutation vocabulary.
  • Files touched only by shell commands stay unmarked. A terminal card names a working directory, not files, so a redirect that writes a file records no location to fold. It still appears in the tree, because the tree reads disk.
  • A session with no workspace root has no tree. Saying so beats falling back to some other directory the reader never asked about.

Removing the panel without uninstalling

Override the row by id in the profile's own cordis.patch.yml — do not insert it again, since a second insert of the same id fails the boot with duplicate loader entry id:

- id: session-resources
  disabled: true

Development

Installing from a local checkout needs the file: prefix:

dsh plugin --profile web add -w "file:/path/to/dsh-plugins/packages/session-resources"

pnpm links rather than copies: the package directory in the profile's node_modules is a junction into the store, and each file inside it is a hard link to the file in your checkout — one inode, several names. Editing a file in place therefore reaches every profile at once, with no install step.

Adding or deleting a file is the case that needs one, since a new file has no link yet. So does any editor that saves by writing a temporary file and renaming it over the original, which replaces the inode and quietly leaves the profile pointing at the old content. Re-linking is cheap, so the habit worth having is to run it after a change rather than to reason about which kind it was:

dsh plugin --profile web install

A running app does not re-read any of this. The browser picks up a client change on reload, but the host half is loaded once at boot — restart dsh web after touching it. The desktop shell loads both at startup, so it needs a restart either way.

License

MIT. The vendored Lucide outlines are ISC; see assets/file-icons/LUCIDE-LICENSE.txt.

Keywords

deepseek-harness

FAQs

Package last updated on 16 Aug 2026

Related posts