dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A modular local AI model ecosystem for DeepSeek Harness (DSH).
DSH is the agent — it reasons, plans, calls tools, and orchestrates workflows. It is not the model runtime. The specialised models (text, image, 3D, audio, video) are external and local, reached through a capability interface. DSH never learns how to launch Stable Diffusion, which Python file starts a mesh generator, or how a given engine manages its process. It only asks:
What capabilities are available?
Everything below that question is this project's job.
┌──────────────────────────────────────────────────────────────────────┐
│ DeepSeek Harness agent reasoning, planning, │
│ ───────────────── tool invocation, workflows │
│ │
│ tools: invoke_model · list_models · list_capabilities · │
│ get_model_status · start_model · stop_model · … │
└───────────────────────────────┬──────────────────────────────────────┘
│ dsh-ai-model-hub DSH plugin
│ (the ONLY DSH-aware code)
┌───────────────────────────────▼──────────────────────────────────────┐
│ ModelHub facade invokeModel({ capability }) │
│ ┌────────────────┬──────────────────┬───────────────────────────┐ │
│ │ Capability │ Model Router │ Runtime Manager │ │
│ │ Catalog │ deterministic │ lifecycle · health · │ │
│ │ registry + │ capability-first │ idle timeout · resources │ │
│ │ validation │ selection │ │ │
│ └────────────────┴──────────────────┴───────────────────────────┘ │
│ ┌──────────────────────────────┬────────────────────────────────┐ │
│ │ Model Adapters │ Artifact Store │ │
│ │ openai_compatible · http_json│ durable, typed, addressable │ │
│ │ comfyui · three_d · │ text/image/audio/video/3D/… │ │
│ │ mock (tests only) │ │ │
│ └──────────────────────────────┴────────────────────────────────┘ │
└───────────────────────────────┬──────────────────────────────────────┘
│ spawn (argv, never a shell string)
┌───────────────────────────────▼──────────────────────────────────────┐
│ Local engines: Ollama · llama.cpp · A1111/Forge · ComfyUI · │
│ TRELLIS · Hunyuan3D · Stable Fast 3D · TripoSR · … │
└──────────────────────────────────────────────────────────────────────┘
Live as a DSH profile plugin. The package installs as one unit — dsh plugin add mounts it, so the hub is composed on every boot — and it serves real local
engines only: Ollama for text, ComfyUI / A1111 / Forge for images, llama.cpp for
a second text engine, and any local image-to-3D Gradio app for meshes. There are no
mock models anywhere in the shipped catalogs and no mock fallback at runtime: a
wrong model name or a stopped engine is a loud error (or a cold start), never
fixture output.
The whole architecture — catalog → routing → runtime → adapter → artifact → chained workflow — is covered by the test suite, which uses an in-process mock adapter as a test double. That adapter is not a model: nothing ships it in a catalog, and no deployment can reach it by accident.
| Phase | Scope | Status |
|---|---|---|
| 1 | Full architecture, DSH plugin, tests | ✅ done |
| 2 | Real local text model (Ollama / llama.cpp / vLLM / LM Studio) | ✅ done — openai_compatible adapter ships |
| 3 | Real local image model (A1111 / ComfyUI) | ✅ done — http_json and comfyui adapters ship |
| 4 | Real local 3D model | ✅ done — three_d adapter ships; see docs/three-d.md |
| 5 | Runtime lifecycle + resource-aware routing | ✅ done — the machine is probed and routed against |
| 6 | Multi-model workflows | ✅ done |
Phase 2 needs no new code: the openai_compatible adapter is implemented, and
config/examples/real-models.example.json contains ready-to-copy Ollama and
llama.cpp entries. Phase 3 is implemented by two adapters, because the two
engine families are genuinely different:
http_json — engines that answer one request with a finished image:
AUTOMATIC1111, Forge, and stable-diffusion.cpp's sd-server. It POSTs the
familiar /sdapi/v1/txt2img body and decodes the base64 images that come back.comfyui — ComfyUI takes an API-format node graph, not a prompt endpoint.
Each explicitly configured workflow declares capabilities, exact input bindings
and a typed output selector. The adapter treats graph internals as opaque, edits
only declared inputs, queues on /prompt, polls /history, and retrieves the
declared output. No checkpoint or saved-workflow enumeration creates providers.Phase 4 is three_d: one adapter that speaks the shape the local 3D ecosystem
actually has — a Gradio queue API or a JSON HTTP route, one or two calls, a mesh
file at the end — so TRELLIS, Hunyuan3D, Stable Fast 3D, and TripoSR are catalog
entries rather than code. It writes a typed model_3d artifact (GLB, GLTF, OBJ,
STL, or PLY, sniffed from the bytes), and a discoverer verifies a declared engine
against the API surface it actually exposes before publishing anything.
Phase 5's remaining gap is closed: ModelHub now probes RAM, VRAM, free VRAM, and
disk, re-measures on demand, and routes against measured headroom — so a model
that would not fit beside what is already loaded is refused with the shortfall in
the message instead of crashing inside the engine.
All adapters are registered by default. See
docs/adding-a-model.md, docs/three-d.md,
and the protocol templates in config/workflows/.
One command, on a machine that already has Node ≥ 22.6 and DeepSeek Harness:
# into the profile behind `dsh web` — the normal case
dsh plugin --profile web add github:Mhmd7-7/dsh-ai-model-hub
This package is the plugin: it declares dsh.bundle.patch, so dsh plugin add installs it and appends dsh-ai-model-hub to dsh.profile.bundles. The
profile composes that bundle on every boot, which is what makes the plugin
live, not merely installed. No profile file is edited by hand and no
cordis.patch.yml has to be written.
Then restart DeepSeek Harness and ask:
List the available AI model capabilities.
| What you want | Command |
|---|---|
| Another profile | dsh plugin --profile hubtest add github:Mhmd7-7/dsh-ai-model-hub |
| A pinned release | dsh plugin --profile web add github:Mhmd7-7/dsh-ai-model-hub#v0.7.0 |
| A published copy | dsh plugin --profile web add dsh-ai-model-hub |
| A checkout you are editing | node --no-deprecation scripts/install-plugin.mjs --profile web |
The last one links the working tree instead of copying it, so the plugin loads
your sources; run npm run build there first if you edited anything under
src/ or dsh-plugin/.
install.ps1 and install.sh remain one-line wrappers for the first row, and
keep their flags (-Profile, -Ref, -Repository, -SkipDoctor):
irm https://raw.githubusercontent.com/Mhmd7-7/dsh-ai-model-hub/main/install.ps1 | iex
curl -fsSL https://raw.githubusercontent.com/Mhmd7-7/dsh-ai-model-hub/main/install.sh | sh
To remove an installation:
dsh plugin --profile web remove dsh-ai-model-hub
The plugin tracks the DSH 0.x line rather than a single release:
| Supported | @deepseek-ai/dsh* >=0.1.5-rc.2 <0.3.0 |
| Developed and tested against | 0.1.5-rc.2 (this package's own tree) and 0.2.0-rc.2 (a current host) |
Two independent things decide whether that holds, and they fail in different places — which is worth knowing, because only one of them is visible in the plugin's own logs.
The manifest gate runs first, before any plugin code. DSH reads
peerDependencies and refuses the row when a @deepseek-ai/dsh* range does not
match the harness. For a 0.x version, caret pins the minor: ^0.1.5-rc.2
means >=0.1.5-rc.2 <0.2.0, so it silently refuses every 0.2.x host. That is
why the peers here are written as explicit ranges and why
tests/peer-range.test.ts exists — it asserts against the harness's own
evaluatePluginCompatibility when that package is installed, and against a
written-out range matcher when it is not. The old caret form is demonstrated
failing in the same file, so it cannot come back unnoticed.
Then the runtime surface is detected, not assumed. dsh-plugin/compat.ts is
the only module that touches a host member. It reads the context once, reports
what it found, and every registration goes through a helper that contains a
missing or differently-shaped member to one diagnostic line:
| If the host has no… | What is lost | What is not lost |
|---|---|---|
tools | everything | nothing — the load fails, by name, on purpose |
systemPrompt | the capability snapshot in the prompt | every tool; the agent can still call list_capabilities |
webServer | the Local models and Priorities pages | every tool |
effect | stopping the hub's processes on plugin unload | every tool; the effect body is not run at all, because nothing could call its disposer |
skills | the bundled skill | every tool |
sandboxPolicy | artifacts in the calling session's workspace | every tool; artifacts fall back to the hub's own store |
get | nothing | the injected-property path still resolves every optional service |
logger | host-formatted diagnostics | the diagnostics — they go to the console instead |
tools is the one hard requirement. Without a tool registry there is nothing for
this plugin to do, and a hub the agent cannot reach is worse than an honest
failure: it produces a settings page and the false impression that the capability
is available.
Two things are deliberately not wrapped. defineTool, because it is a pure
function from a closed input to a closed output whose signature has not moved
across the whole 0.1.5-rc → 0.2.0-rc line. And the import type lines, which
vanish at compile time — a host that renames a type cannot break a running plugin.
The complete runtime DSH coupling is six value imports: defineTool,
schemastery's default export, and cordis's Service.
The plugin's sources are TypeScript, and Node runs them directly through type
stripping while you work in a checkout. It refuses to do that inside
node_modules:
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING: Stripping types is currently
unsupported for files under node_modules
A package installed by name — from GitHub, from a registry — lands inside the
profile's node_modules, so a .ts entry there could never be imported. That is
the entire reason this repository commits lib/: lib/ is the same sources
compiled to JavaScript, and the package's entry points (main, exports) point
at it, so dsh plugin add needs no build step on your machine.
lib/ is generated, never edited: npm run build regenerates it from src/ and
dsh-plugin/. The one setting that makes it work is
rewriteRelativeImportExtensions, so the emitted JavaScript imports ./x.js
where the source said ./x.ts.
There are two halves, and you can use either without the other.
cd dsh-ai-model-hub
npm install # typescript + the DSH packages the plugin links against
npm test # the suite, ~20 s, no engines or GPU required
npm run typecheck # strict TypeScript, no errors
npm run build # compile src/ + dsh-plugin/ into lib/
npm run demo # the vertical slice, end to end
npm run demo:workflow # a multi-step cross-model pipeline
npm run demo:3d # image → 3D → a real .glb, against a stand-in engine
The demo prints the full path — capability discovery, routing with reasons, invocation, and the artifact on disk:
=== catalog: capability discovery ===
text_to_image text → image via comfyui_z_image_turbo
text_to_text text → text via ollama_text_model
not available here: image_to_3d, image_to_image, audio_generation, …
=== routing ===
chose comfyui_z_image_turbo
rationale: comfyui_z_image_turbo (score 1): …
[rejected] ollama_text_model: does not declare capability "text_to_image"
=== artifact ===
valid PNG: yes
size: 1.2 MiB
It runs against config/models.json, so it needs the engines that catalog names:
Ollama for text, ComfyUI for images. node examples/vertical-slice.ts <catalog>
takes another catalog, and the last section deliberately asks for a capability
nobody serves, to show what a refusal looks like.
npm run demo:3d needs nothing installed. It starts a stand-in 3D engine inside
the example and runs the whole real path against it — discovery, health,
resource-aware routing, a two-step Gradio protocol, and a .glb written to disk —
then prints every HTTP request the engine saw, to make the boundary concrete. To
point it at TRELLIS instead, swap the stand-in for your catalog entry; the three
capability calls are identical. See docs/three-d.md.
# Installs into the 'web' profile (the one behind `dsh web`), then verifies.
npm run install:plugin
# ...or target another profile:
node --no-deprecation scripts/install-plugin.mjs --profile <name>
Then restart DeepSeek Harness and ask the agent:
List the available AI model capabilities.
npm run doctor # inspects: is it loadable, and if not, why not
node scripts/smoke.mjs --profile web # acts: applies the plugin and lists what it registered
doctor inspects and reports; smoke actually runs the plugin against a real
cordis context and prints the tools it registered. Use smoke when you want
proof rather than a checklist:
Registered tools:
check_model_health explain_routing get_model_status invoke_model
list_artifacts list_capabilities list_models start_model
stop_model refresh_model_discovery
Catalog: model hub ready: 2 model(s), 2 capability(ies) from …/config/models.json
Note on flags.
npm runforwards flags to the script, but npm 12 parses--profileas its own config flag and fails. Use the directnode scripts/smoke.mjs --profile <name>form for a non-default profile.
If the harness dies with:
invalid plugin, expect function or object with an "apply" method, received object
then the row cordis.patch.yml inserts is pointing at something that is not a
plugin. In this package that can only happen if the row's name was changed
away from the bare package name: dsh-ai-model-hub resolves to the package's
. export, which is the plugin (lib/dsh-plugin/index.js, exporting
name/inject/apply/Config). The hub library lives behind the
dsh-ai-model-hub/library subpath instead, precisely so the two cannot be
confused.
The row has two names, and they are easy to conflate: id is the row's name in
the composed tree (what a profile's own cordis.patch.yml uses to override or
disable it), while name is the module specifier the loader imports. Keep
name bare — a subpath such as dsh-ai-model-hub/plugin would load the host half
but silently drop the Web UI half, because DSH's client module system only
resolves a row's package manifest from a bare specifier. cordis.patch.yml
documents this in place, and npm run doctor checks it.
The plugin resolves its catalog from ordered anchors, first hit wins:
configPath — an explicit file, if configured. When set it is the only
candidate: a wrong path is an error, never a silent fallback.searchRoots, for a deployment that names its own.At every level discovery accepts models.json, model-catalog.json, or
dsh-ai-model-hub.json, directly or in a config/ subdirectory.
Anchor 4 is what makes the zero-config case work. DSH installs profile plugins as
junction links and Node resolves them to their real path, so the plugin finds the
config/models.json shipped by the checkout it was installed from — no matter
where the host was launched. Anchor 3 covers the opposite case: a host started
inside a project that carries its own catalog.
Why the host's working directory and not the session's? A plugin's
applyruns when the profile boots — before any session exists — and one host serves many sessions with different workspaces, so there is no single "session working directory" to read at that moment. Anchors 1 and 2 are the explicit way to name a workspace. Per-session resolution is a deliberate future step, not an oversight; see docs/roadmap.md.
If no anchor yields a catalog the plugin logs model hub disabled together with
every directory it tried, registers no tools, and lets DSH boot normally — a
missing or broken catalog never makes the agent unusable.
dsh plugin add <path> links the working tree — DSH installs profile plugins
as junction links — so Node resolves the plugin to its real path in the
repository instead of to a copy inside the profile. Peer dependencies then
resolve by walking up from the package: from lib/dsh-plugin/ to
<repo>/node_modules. Run npm install once in the repository and that
directory exists, because dsh plugin add forwards to pnpm, which only warns
about peer dependencies instead of installing them.
This is not theoretical: it was found by installing into a throwaway profile,
deleting the workspace's node_modules, and watching the plugin fail to resolve
@deepseek-ai/schemastery.
A linked checkout also loads lib/, exactly like an installed copy does — so run
npm run build after editing src/ or dsh-plugin/. If you would rather not
think about that, work on the hub as a library (npm test, npm run typecheck)
and only build when you want the plugin to pick the change up.
The historical obstacle on the GitHub route — Node refusing to strip types for
files under node_modules — is what committing lib/ removes; see Why the
package ships compiled JavaScript.
| Ask the agent | Tool it calls |
|---|---|
| "What AI models can you run?" | list_models |
| "What can this machine actually do?" | list_capabilities |
| "Create a futuristic city image." | invoke_model (text_to_image) → router picks the model |
| "Now make a 3D model from that image." | invoke_model (image_to_3d), chaining the artifact id |
| "Why did it pick that model?" | explain_routing — read-only |
| "Is the image model running?" | get_model_status, check_model_health |
| "Start / stop the image model." | start_model, stop_model |
The agent never names an engine, a command, or a path. That is the whole design.
The plugin also teaches the agent how to use it.
skills/dsh-ai-model-hub/SKILL.md is registered
as a skill provider on ctx.skills, the way DSH's own bundled dsh-badge skill is,
so:
~/.dsh/skills or a project skill root, and no profile
file is edited — installing or updating the plugin installs the skill;git pull changes what the agent
reads on its next load, with no reinstall step and no stale copy;The registration is deliberately not a declared inject. It goes through
ctx.inject(['skills'], …), so a composition with no skill registry — headless, or
the sdk-minimal profile — still gets the model tools and simply never runs the
skill callback. A missing or malformed skill file costs one warning and the skill,
never the tools beside it.
The file is an ordinary skill, so it also works the old-fashioned way: link or copy
skills/dsh-ai-model-hub/ into a .dsh/skills root if you would rather not run the
plugin at all. Its frontmatter is the single source of truth for the name and
description on both paths.
The agent is not the only one who needs to know where the engines are. The plugin also adds a Local models section to the DSH settings, which answers the question a user actually has: which engines are on this PC, where is each one installed, is it running, and what is inside it.
| Row | Where it comes from |
|---|---|
| Verified installation | Engine markers or executables in configured/well-known locations; a configured cwd alone is not proof |
| Configured paths | Catalog launch paths, displayed separately from verified installation paths |
| Listens on / availability | Independent bounded endpoint checks on both Scan and Refresh |
| Hub can start it | The descriptor's startable, after the deployment's allowProcessLaunch |
| Catalog models / capabilities | The same live hub the tools read, so the page cannot disagree with behaviour |
Scan is the page's main action, and it produces one list of two kinds of thing, each row labelled with what it is:
| Kind | What it is | Where it comes from |
|---|---|---|
| Ollama Model | An observed or catalog-known model | Live /api/tags, offline loopback-host manifests (OLLAMA_MODELS or ~/.ollama/models), active catalog, or last-good observations |
| ComfyUI Workflow | One complete workflow or an explained unreadable entry | Saved /userdata workflows, verified installation's workflow store, configured directories, and active catalog inline/pinned workflows |
A ComfyUI workflow is the smallest thing the list exposes. Its checkpoints, LoRAs, VAEs, text encoders, ControlNets and individual nodes are not listed — they are the workflow's own business, which is why changing what a workflow loads never changes anything an agent or a user has to know.
The two halves are discovered independently, so an Ollama instance that is down cannot hide the workflows and a ComfyUI that is down cannot hide the models; each source reports its own status. Scanning twice refreshes the list rather than growing it, because every entry's id comes from its source location.
A saved workflow is discovered without a running ComfyUI. Workflow discovery
reads the configured directories, the engine's own saved-workflow store when it
can be found on disk, the active catalog, and the workflows earlier scans
remembered — so a first launch with the engine stopped still lists the graphs that
are on the machine, and an engine that stops mid-session cannot take them off the
screen. The list says which evidence it has: ComfyUI stopped when a refused
connection proves the process is not running, ComfyUI unreachable when a timeout
or a routed address leaves that genuinely unknown, and a cached row whenever the
contract came from a previous observation rather than this pass. Only a scan that
completely read a source can conclude a workflow was deleted; a failure never can.
The remembered workflows live in `~/.dsh/plugins/dsh-ai-model-hub/workflo