
Security News
GPT-6 Astra Attempts Supply Chain Attacks Against Open Source Maintainers in Testing
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.
dsh-plugin-session-resources
Advanced tools
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.
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
-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
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.
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.
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.
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.
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.
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.
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/.
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.glob/grep paths
and web_search sources are real provenance, but one discovery call can name
hundreds of paths nobody opened.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
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.
MIT. The vendored Lucide outlines are ISC; see
assets/file-icons/LUCIDE-LICENSE.txt.
FAQs
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.
We found that dsh-plugin-session-resources demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.

Product
Socket can now send alerts and supply chain attack notifications to Microsoft Teams, with filters that route the right updates to each channel.

Security News
pnpm 12 rewrites the package manager in Rust, cutting install times by up to 90% while preserving pnpm 11 workflows and lockfiles.