reactive-resume
reactive-resume
A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!
HIT-HTML/dsh-ENHANCED
One plugin, every upgrade DeepSeek Harness lacks out of the box: multi-engine web search, skills & MCP managers, per-profile plugin toggles, session cleanup with hover-delete, one-click restart/shutdown, themes. Single tool surface, no telemetry.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:HIT-HTML/dsh-ENHANCED
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
The everyday upgrades DeepSeek Harness (DSH) lacks out of the box — bundled into one plugin. Free multi-engine web search with automatic fallbacks, persistent skill and MCP management, guarded session cleanup, and themes — all wired into a single Settings tab and one model tool. Ships prebuilt, so there's no build step between install and working; and it phones home to no one.
web_fetch — URL content retrieval — fetches any HTTP(S) URL and returns the content as markdown (HTML pages are stripped to readable text). Works out-of-the-box once the plugin loads (uses Node's built-in fetch; no harness fetch provider required). 20 s timeout, up to 5 redirect hops, 256 KB response guard.web_search auto-configures at boot — dsh-enhanced writes free-search: provider: ddg into ~/.dsh/settings.yaml on first run (idempotent; rewrites a stale provider: value). web_fetch needs no setup at all.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.@deepseek-ai/dsh-mcp-client rows across profiles from one place.dsh-base, dsh-web-app, dsh-enhanced) are hard-refused, and disabling an official @deepseek-ai/* plugin requires an explicit confirm.~/.dsh/sessions/ into the operating system's own trash: macOS Finder Trash (~/.Trash), the FreeDesktop Trash on Linux (with .trashinfo metadata so desktops offer Restore), and the Recycle Bin on Windows (via PowerShell). Deletion is dry-run first (plan + token), refuses sessions open in this process or active in the last 15 minutes (another window may still hold them), and if a native move fails, nothing is deleted — you get the exact error instead. Restore is just dragging the folder back under ~/.dsh/sessions/. In the sidebar itself, every session row grows a small trash-can icon on hover next to its ⋯ menu; it drives the same guarded pipeline.One host composition plugin (cordis.patch.yml row), one model tool surface (manage_skills_mcps), one browser card (Settings → Plugins). No telemetry, no external services beyond the search engines themselves. Secrets get the same discipline: keys are written owner-only (0600) and never echoed back, action errors pass secret redaction before reaching the model, the UI, or a log, and destructive flows run dry-run-first behind confirm tokens — with a framework-free selfcheck suite gating every release in CI.
Prerequisite: a working DeepSeek Harness installation — this is a DSH plugin, not a standalone app.
1. Install (one command):
dsh plugin add https://github.com/HIT-HTML/dsh-ENHANCED
The repo ships prebuilt dist/ and client.js, so no build step is needed to install. To develop instead, see Development.
Clone the repo anywhere you like, then add this row to the profile's cordis.patch.yml (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
2. Restart the profile so the new composition row loads (plugins mount at boot).
3. Verify it's alive:
manage_skills_mcps.That's it — and note web search needs zero configuration: fresh installs default to the keyless Bing engine, so your agent can search the moment the profile boots. Visiting Settings → Plugins → Search is purely opt-in — for keyed engines, self-hosted SearXNG instances, exclusions, or cooldown tuning.
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 plugins.ts sessions.ts compact.ts │
│ instance.ts search.ts cooldown.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, plugins,│
│ sessions, compact, search, theme) │
│ session-delete.js hover trash-can delete for native sidebar │
│ rows (fiber-resolved, guarded RPC) │
│ 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:plugins >>> …rows… # <<< dsh-enhanced:plugins <<<
# >>> 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 GUI and the model tool call the same handlers, so they can't drift:
X_ACTIONS (string list) + handleX: Handler.index.ts concatenates all action lists into the manage_skills_mcps tool schema and lines all handlers up in HANDLERS.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.
| Module | Actions | Writes to |
|---|---|---|
| skills | list_skills, read_skill, add_skill, edit_skill, remove_skill, install_skill, install_skill_files |
~/.dsh/skills/<name>/SKILL.md |
| mcp | list_mcps, mcp_status, add_mcp, remove_mcp, enable_mcp, disable_mcp |
managed :mcp: block |
| compact | compact_status, set_compact |
managed :compact: block |
| instance | shutdown_instance, restart_instance |
process control only |
| plugins | list_plugins, set_plugin_enabled |
managed :plugins: block |
| sessions | list_sessions, delete_sessions |
moves session dirs to trash |
| search | list_search, set_search |
managed :search: block |
| fetch | web_fetch |
— (calls Node built-in fetch directly) |
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. Two more
guards: once a key is stored the patch file is written owner-only (0600), and every action
error passes shape-based secret redaction before reaching the model, the UI, or a log —
so a gateway echoing your key back inside an error message gets masked.
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):
| What | Upstream | Here |
|---|---|---|
| settings namespace | free-search |
enhanced-free-search |
| bridge prefix | /api/dsh-free-search-settings |
/api/dsh-enhanced-free-search |
| search provider id | ddg |
ddg (reverted from enhanced-free; harness config expects ddg) |
| settings UI section | installed its own card | removed — our Search tab owns config UX |
| self-update machinery | check-update + pnpm add upgrade routes |
removed — a vendored copy must not reinstall upstream over itself |
| agent-visible strings | "Settings > Plugins > Free Search" | point at this plugin's Search section |
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 "ddg",
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.
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).
excludedEngines): a CSV string or array of engine ids removed from the
chain entirely — typos are rejected, excluding every engine is rejected. Saved in the
:search: block; takes effect after a profile restart.src/cooldown.ts): quota-exhaustion failures put an engine
on a 12 h cooldown, 429/rate-limit wordings on 15 min. State persists to disk
(~/.dsh/dsh-enhanced/cooldown-state.json), so restarts don't re-burn dead quota; generic
network errors and missing keys never cool anything down. Saving search config wipes stale
verdicts (your keys or endpoints changed).exaBaseUrl, tavilyBaseUrl, keenableBaseUrl): point keyed engines
at a self-hosted or proxy gateway; http/https only, blank clears._fallback {from, to, reason} twin alongside the human-readable Note.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 listeningProbe lives in probeInstances() (src/search.ts); statuses ride the list_search response as
instanceStatus[].
| Surface | Where | Notes |
|---|---|---|
| Config tab | Settings → Plugins → Search | provider/region/market/keys/instances |
dsh_search tool |
ours, thin | delegates to active provider, fails clearly if none |
web_search, advanced_search, platform_search, free_search_test |
vendored | standard + time-filtered + per-platform + engine-doctor tools |
| Test bridge | POST /api/dsh-enhanced-free-search/raw-search |
loopback-only; body {query, maxResults?, engine?, timeRange?} — handy for curl smoke tests |
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, block-surgery edge cases (torn blocks, CRLF, rewrite cycles), secret
preservation, YAML shapes, name validation, provider registration, the full session-delete flow
(guards, token, trash), and a smoke render of the built browser bundle.
It is the regression gate — extend it when you add behavior.
CI (.github/workflows/ci.yml): every push to main and every PR runs frozen install → build →
selfcheck on Node 22, gating the shipped prebuilt artifacts.
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.
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.FREE_ENGINES / ALL_ENGINES, the freeEngines chain array inside provider.search,
and a case in runEngineTest.client/sections/search.js: one PROVIDER_META entry (free: true unless it needs a key).curl -X POST .../raw-search -d '{"engine":"x","query":"hi"}'.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.
npm run build && npm run selfcheckdist/ and client.js are committed artifacts here — make sure they're freshgit grep -E "/Users/|sk-[A-Za-z0-9]")package.json version, tag vX.Y.Z matching it, push commits + tag togethercordis.patch.yml and never sent back to the browser
(blank field = unchanged; hasKey booleans only).isLoopbackRequest guard) and redacts secrets.EXA123 in tests are deliberate fixtures, not credentials.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.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: skill-manager、mcp、theme、web-search。