dsh-artifacts
An Artifacts tab for the DSH (DeepSeek Harness) web GUI sidebar: lists the files an agent produced and previews them in place, with live reload.
- Stars
- 1
- Language
- JavaScript
- Created
- Sep 9, 2026
- Updated
- Sep 9, 2026
Introduction
dsh-artifacts
An Artifacts tab for the DSH (DeepSeek Harness) web GUI sidebar.
Agents produce files — an HTML report, a dashboard, a diagram, a CSV. This tab lists them newest-first inside the sidebar and previews one in place, so a human watching a session can see what it produced without copying paths into a browser by hand.
It is a peer of the sidebar's own tabs (explorer, editor, terminal, browser), not an overlay: it registers through the same public extension point they use.
What it does
- Registers one tab,
dsh-artifacts:library, viactx.betterSidebar.registerTab. - Lists the artifact directory newest-first with a type badge, size and age.
- Click a row to preview it in an iframe inside the tab, using the relative URL the index supplies, so it is same-origin with the GUI and loads with the browser's existing session cookie.
Open ↗opens it in a real browser tab,Copy linkcopies the shareable absolute URL,Reloadre-fetches the preview,Refreshre-reads the index.- Polls every 5 s only while the tab is active and the panel is open, using
the
visibleprop the sidebar provides for exactly that. - Toolbar and row geometry are fixed-box on purpose: no label or metric changes width with state, so a poll re-render cannot make the bar jitter.
If your server injects a live-reload snippet into served HTML (see
docs/artifact-index-endpoint.md for a reference implementation), previews
refresh themselves when a file is regenerated. This plugin does not implement
that and does not need to know about it.
Requirements
- DSH web GUI with
dsh-better-sidebarmounted (entry idbetter-sidebar). If the registry is missing, the client half logs an error and registers nothing — it never throws, so a missing dependency degrades to "no tab" rather than a broken sidebar. - Some same-origin HTTP endpoint answering the small JSON index contract in
docs/artifact-index-endpoint.md. DSH does not define an artifact directory or an index route itself, so this is the one piece you wire up. A standalone reference server and a publisher script are incontrib/.
Install
# from GitHub
dsh plugin --profile web add github:lucagiftzek/dsh-artifacts
# or from a checkout
git clone https://github.com/lucagiftzek/dsh-artifacts
dsh plugin --profile web add link:./dsh-artifacts
dsh plugin is a pnpm passthrough, so add also appends the package to
dsh.profile.bundles. Removing is dsh plugin --profile web remove dsh-artifacts.
Activation needs a dsh-web restart, which mints a new launch token and
kills any session in flight. Rebuilds of lib/client.js afterwards are picked
up live — the served rev changes and a browser refresh is enough.
Point it at your artifacts
The default index URL is /report/?list=1. Override it per browser without a
rebuild:
localStorage['dsh-artifacts:indexUrl'] = '/artifacts/index.json'
The endpoint must return:
{ "count": 2, "items": [
{ "name": "report.html", "url": "/report/report.html", "ext": "html",
"size": 11160, "mtime": 1788934528 }
] }
url may be relative or absolute. Anything else the plugin can render without
(badge, a missing size) degrades quietly. Full contract, plus how to keep it
metadata-only and fenced to one directory, is in
docs/artifact-index-endpoint.md.
Architecture: why the node half is empty
lib/index.js imports nothing and registers nothing. That is deliberate, and
both reasons are failures worth designing against:
- It still has to exist. The client-modules host scan reads the
dsh.clientdeclaration from packages mounted in the host Loader. No mount, no served bundle. - A bad host import is fatal, not local. A DSH profile this plugin's author
runs was taken down by exactly that: two plugins imported symbols from
@deepseek-ai/dsh-settingsthat the installed version does not export. The ESM import became a hardSyntaxErrorat load, the loader entry threw, anddshexited 1 before boot finished — hundreds of restarts and the site fully down. A zero-import host half cannot fail that way. - An unsatisfied
injecthangs boot.inject: []on the host half avoids the "1 entry did not activate" failure that aninjectnothing provides causes.
All data reaches the browser over HTTP, so the plugin needs no host service, no filesystem access and no settings namespace.
Build
pnpm install # esbuild is a devDependency
node scripts/build.mjs # writes lib/client.js (+ map)
Set ESBUILD_MAIN=/path/to/esbuild/lib/main.js to build against a bundler that
lives elsewhere, which keeps the build offline.
The client bundle is CJS wrapped in the web boot factory
(window.__ModuleLoader__.load({ id, factory })). react and
react/jsx-runtime stay external and resolve to the shell's own instances — the
served URL is the combo form /plugins/??dsh-artifacts/client.js&rev=…, not
a plain per-plugin path. Two gates run after the build:
- purity — no
@deepseek-ai/may reach the bundle; a value import would inline a duplicate runtime instance of a shell package. - seed words — every bare
requiremust be a declared external, or the module system throws loudly at materialization.
Test
node test/run.mjs # offline: no browser, no DSH_HOME, no network
test/render.mjs materializes the bundle the way the browser module system
does, drives registerTab, and structurally renders six states — empty library,
populated with a selection, hidden tab, fetch error, vanished selection, and
toolbar geometry under busy. It seeds the real react/jsx-runtime so
elements are genuine, with a programmable stub for react so hooks need no
dispatcher.
test/fetch.mjs captures load() from the poll interval and drives every fetch
outcome, including the auth-redirect case where a proxy answers HTML instead of
JSON.
Both suites have already earned their keep. The render suite caught a vanished
selection being synced in an effect, which painted one frame with an iframe
pointing at a path that had just 404'd — it is derived during render now. It
later caught a toolbar that changed width on every poll because a button label
swapped with busy.
For agents
skills/publish-artifact/SKILL.md is a
drop-in skill teaching an agent the convention this tab reads: where to write
artifacts, how to name them, and what never to put in one. Copy it into
$DSH_HOME/skills/, ~/.agents/skills/, or a project's .dsh/skills/.
License
MIT — see LICENSE. Copyright (c) 2026 Loukas "Luca" Tzekos.
Do whatever you want with it. The only ask is attribution: keep the copyright line, and if you fork it publicly, a "based on dsh-artifacts by Luca Tzekos" note in your README is appreciated. See NOTICE.