Back to home@HIT-HTML

dsh-ENHANCED

Everyday upgrades for DeepSeek Harness in one plugin: multi-engine free web search, skills & MCP management, auto-compact tuning, instance controls:RESTART/SHUTDOWN, themes

Stars
2
Language
JavaScript
Created
Aug 24, 2026
Updated
Aug 25, 2026

Introduction

dsh-enhanced

preview

One plugin that bundles the everyday upgrades DeepSeek Harness (DSH) lacks out of the box:

  • Free web search — a vendored multi-engine search provider (DuckDuckGo ×2, Bing, AnySearch, SearXNG, Mojeek-ready fallback chain + optional paid engines), configured from a Settings tab, with self-hosted-SearXNG support and live health checks.
  • Skills manager — install/edit/remove agent SKILL.md skills persistently. Installs accept a single skill or a folder OF skills (disk path or browser folder-picker alike), keep bundled scripts/ executable (browsers drop permission bits; shebang files are restored to 0755), and report per-skill results so one bad bundle never blocks the rest.
  • MCP server manager — manage @deepseek-ai/dsh-mcp-client rows across profiles.
  • Auto-compact tuner — clamp the context-compaction trigger below the harness default.
  • Instance controls — clean shutdown/restart of the GUI process via icon buttons beside Settings in the sidebar foot.
  • Themes — original ENHANCED theme (phosphor-green terminal look, digital-rain boot intro) and a Cyberpunk 2077 theme ported from the community theme.

Single host composition plugin (cordis.patch.yml row), single model tool surface (manage_skills_mcps), single browser card (Settings → Plugins). No telemetry, no external services beyond the search engines themselves.


Install

dsh plugin add https://github.com/HIT-HTML/dsh-ENHANCED   # or clone into a profile and add a composition row

The repo ships prebuilt dist/ and client.js, so no build step is needed to install. To develop, see Development.

Plugin row (usually written for you by dsh plugin add):

- id: dsh-enhanced
  name: dsh-enhanced
  config:
    mcpProfiles: ["default", "web"]   # whose cordis.patch.yml receives managed rows
    allowRestart: false               # opt-in for supervisor-assisted restarts

Architecture

Two halves, standard DSH plugin shape:

┌─ HOST (Node, src/*.ts → dist/) ─────────────────────────────┐
│ index.ts   composition root: registers the model tool,      │
│            browser RPC channel, settings anchor, boots the  │
│            vendored search engine                           │
│ shared.ts  paths, managed-block surgery, Env/Handler types  │
│ skills.ts  mcp.ts   compact.ts   instance.ts   search.ts    │
│            feature modules — each owns its actions end-to-end│
│ free-search-vendor.ts   vendored engine (see below)         │
└──────────────┬──────────────────────────────────────────────┘
               │ package-private JSON RPC (browser → host)
┌─ CLIENT (browser, client/** → client.js bundle) ────────────┐
│ core.js        draft/save pipeline, sections registry       │
│ main.js        boot, saved-theme activation                 │
│ sections/*     one card per feature (skills, mcp, compact,  │
│                search, theme)                               │
│ themes/*       matrix, cyberpunk2077 (+ boot intro)         │
└─────────────────────────────────────────────────────────────┘

Persistence is deliberately boring: every host feature writes marker-delimited "managed blocks" into <profile>/cordis.patch.yml (and ~/.dsh/skills/ for skills). No database, no state file — the user's config tree is the state, readable by eye:

# >>> dsh-enhanced:mcp >>>     …rows…      # <<< dsh-enhanced:mcp <<<
# >>> dsh-enhanced:compact >>> …row…       # <<< dsh-enhanced:compact <<<
# >>> dsh-enhanced:search >>>  …row…       # <<< dsh-enhanced:search <<<

shared.ts owns the split/merge (splitBlock, splitInner); feature modules never regex the file themselves.

The action-core pattern (how everything stays in sync)

The GUI and the model tool call the same handlers, so they can't drift:

  1. A feature module exports X_ACTIONS (string list) + handleX: Handler.
  2. index.ts concatenates all action lists into the manage_skills_mcps tool schema and lines all handlers up in HANDLERS.
  3. Dispatch = first handler to return non-null wins; unknown action ⇒ error.
  4. Browser RPC reuses the same performAction core over a package-private channel.

To add a feature: new src/<feature>.ts exporting ACTIONS + Handler, two lines in index.ts, one section file under client/sections/. That's the whole integration story.


Feature reference

ModuleActionsWrites to
skillslist_skills, read_skill, add_skill, edit_skill, remove_skill, install_skill, install_skill_files~/.dsh/skills/<name>/SKILL.md
mcplist_mcps, mcp_status, add_mcp, remove_mcp, enable_mcp, disable_mcpmanaged :mcp: block
compactcompact_status, set_compactmanaged :compact: block
instanceshutdown_instance, restart_instanceprocess control only
searchlist_search, set_searchmanaged :search: block

Client-side, each feature is a section plugged into three registries in core.js: DRAFT_SHAPES (form state), DIRTY_CHECKS (unsaved chip), SAVE_STEPS (replay on Save). Sections self-fetch on expand and stage edits locally; Save replays steps in order and a mid-batch failure keeps exactly the unapplied part staged.

Secrets (API keys) are never echoed back: list_search reports hasKey.<field> booleans, key inputs start blank meaning "unchanged", and only non-blank values are written.


Search subsystem

Provenance

The engine is vendored from dsh-free-search v0.4.12 (MIT, © DDDMUC) into src/free-search-vendor.ts. We vendor rather than depend because upstream's peerDependencies aren't published to npm — fresh installs of the standalone plugin can fail outright. Upstream fixes do not propagate automatically; re-port when adopting them.

Local adaptations vs upstream (all collision-safety or de-branding):

WhatUpstreamHere
settings namespacefree-searchenhanced-free-search
bridge prefix/api/dsh-free-search-settings/api/dsh-enhanced-free-search
search provider idddgenhanced-free
settings UI sectioninstalled its own cardremoved — our Search tab owns config UX
self-update machinerycheck-update + pnpm add upgrade routesremoved — a vendored copy must not reinstall upstream over itself
agent-visible strings"Settings > Plugins > Free Search"point at this plugin's Search section

Boot flow

profile cordis.patch.yml          dsh-enhanced host boot
:search: managed block ─┐
                        ├─► index.ts: readSearchConfig(profiles[0])
Settings→Search tab ────┘         │
                                  ▼
                    ctx.inject(["web"], scope => freeSearch.apply(scope, cfg))
                                  │
                    registers provider id "enhanced-free",
                    agent tools, system-prompt section, bridge routes
                                  │
                                  ▼
              harness web_search / advanced_search route through it

Config is read once at boot — after saving in the tab, restart the profile to apply. If the standalone dsh-free-search is also installed somewhere, nothing clashes: distinct namespace/prefix/provider-id, and each registration is guarded.

Engines and the fallback chain

Preferred engine = your Provider dropdown. On failure/empty results the chain walks on:

paid (only if keyed):  exa → tavily → keenable → perplexity → deepseek-official
free, always:          bing → anysearch → ddg → ddg-lite → searxng

Time filtering (advanced_search) is honored by engines that support it and skips the rest. Results are cached per query (LRU ~50, TTL ≤5 min, configurable).

SearXNG instances (self-hosted path)

Public SearXNG instances rate-limit the JSON API to death; run your own:

mkdir -p ~/.searxng && cat > ~/.searxng/settings.yml <<'EOF'
use_default_settings: true
server:
  secret_key: "pick-any-random-string"
  limiter: false
search:
  formats:
    - html
    - json        # required — without this every API query gets 403
EOF
docker run -d --name searxng -p 8888:8080 -v ~/.searxng:/etc/searxng searxng/searxng

Paste http://127.0.0.1:8888 into the Search tab (SearXNG provider). Saved URLs are normalized at the parser in src/search.ts (set_search): missing scheme ⇒ http://, trailing slashes stripped, comma/space/newline separators, multiple URLs tried in order. A bad URL can never break searching — dead instances are skipped and the chain falls through.

Health checks: whenever the Search tab loads (and right after Save), list_search probes each saved URL (2.5 s cap, parallel) and renders one line per instance:

  • ✓ ok — real SearXNG JSON answered
  • ⚠ misconfigured — reachable but JSON API off (the missing-formats: json mistake)
  • ⚠ down — nothing listening

Probe lives in probeInstances() (src/search.ts); statuses ride the list_search response as instanceStatus[].

Surfaces

SurfaceWhereNotes
Config tabSettings → Plugins → Searchprovider/region/market/keys/instances
dsh_search toolours, thindelegates to active provider, fails clearly if none
web_search, advanced_search, platform_search, free_search_testvendoredstandard + time-filtered + per-platform + engine-doctor tools
Test bridgePOST /api/dsh-enhanced-free-search/raw-searchloopback-only; body {query, maxResults?, engine?, timeRange?} — handy for curl smoke tests

Development

npm install
npm run build        # tsc → dist/, then client/** → client.js (+ node --check)
npm run selfcheck    # offline end-to-end test, no frameworks

selfcheck (test/selfcheck.mjs) builds a temp $DSH_HOME, runs the real handlers against a stubbed plugin context (captured registrations instead of live services), and asserts disk output: managed-block round-trips, secret preservation, YAML shapes, name validation, provider registration. It is the regression gate — extend it when you add behavior.

Lab bench (second live GUI without touching your main one):

echo $'webserver:\n  port: 3090' > /tmp/lab-port.yml
dsh --profile lab --patch /tmp/lab-port.yml

Serves the workspace client live (cache-control: no-cache), so client edits show on refresh; host edits need a lab restart. Never point this at your main profile.

Adding an engine (search)

  1. src/free-search-vendor.ts: const X_URL, async function searchX(query, maxResults, options, signal) returning { sources: [{url,title?,snippet?}], truncated:false } — reuse fetchHtmlWithRetry, stripTags, uniqueSources.
  2. Add the id to FREE_ENGINES / ALL_ENGINES, the freeEngines chain array inside provider.search, and a case in runEngineTest.
  3. client/sections/search.js: one PROVIDER_META entry (free: true unless it needs a key).
  4. Smoke-test through the bridge: curl -X POST .../raw-search -d '{"engine":"x","query":"hi"}'.

Adding a feature module

Copy the shape of src/compact.ts (smallest full example): actions const, Handler switch, managed block via splitBlock/writeFile, guard-rails server-side. Then one handleX line + X_ACTIONS spread in index.ts, and a section file registering into the client registries.

Release checklist

  • npm run build && npm run selfcheck
  • Lab bench smoke: bridge query with preferred engine + one fallback
  • dist/ and client.js are committed artifacts here — make sure they're fresh
  • No machine-specific paths or secrets in tracked files (git grep -E "/Users/|sk-[A-Za-z0-9]")
  • Bump package.json version

Privacy & security notes

  • No telemetry. The only outbound traffic is the searches you (or your agent) run.
  • API keys are stored in the profile's cordis.patch.yml and never sent back to the browser (blank field = unchanged; hasKey booleans only).
  • The test bridge accepts loopback requests only (isLoopbackRequest guard) and redacts secrets.
  • Strings like EXA123 in tests are deliberate fixtures, not credentials.

License

MIT — see LICENSE. Contains code vendored from dsh-free-search (MIT, © DDDMUC) and theme work originating from the Cyberpunk 2077 community theme; both attributed in-file.