louisyeaaah/dsh-reload ↗★ 0

dsh-reload

Hot-reload DSH plugins and skills without restarting: reload_plugin re-imports a loaded plugin's own modules and re-applies it (with rollback), reload_skill forces skill re-discovery and diagnoses why a skill is invisible. Ships a skill and two slash commands. 适合插件开发者,用于在修改代码后快速热重载而无需重启应用。

Package
dsh-reload
Compatibility
Unverified
Harness peer range
>=0.2.0-rc.1 <0.3.0-0
Version
0.1.5
License
MIT
Last updated
Oct 3, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:louisyeaaah/dsh-reload

Usage

reload_plugin({})                                     # 1. list the plugin rows: package name, entry id, running or not
reload_plugin({ name: 'dsh-video' })                  # 2. reload by package name (entry id works too: 'tool-video')
reload_plugin({ name: 'dsh-video', dryRun: true })    # 3. preview which modules would be cleared, change nothing
reload_skill({ name: 'my-skill' })                    # 4. force re-discovery and diagnose this skill

reload_plugin({ filter: '@deepseek-ai/' }) lists every row instead of the default (third-party and local rows, plus every row that is not running). reload_skill() without a name only refreshes; reload_skill({ cwd: '/path/to/repo' }) resolves project skill roots from another directory (an absolute path; ~ is not expanded). Humans can type /reload-plugin dsh-video and /reload-skill my-skill instead.

Real output (the plugin reloading itself, v0.1.1 → v0.1.2; only the path prefix is shortened to ``):

✅ Reloaded: include:tool-reload (dsh-reload)
Module: file:///profiles/
/node_modules/dsh-reload/lib/index.js
Package root: /profiles/
/node_modules/dsh-reload
Local modules cleared and re-evaluated (5):
  • file:///profiles/
/node_modules/dsh-reload/lib/index.js
  • file:///profiles/
/node_modules/dsh-reload/lib/plugin-reload.js
  • file:///profiles/
/node_modules/dsh-reload/lib/shared.js
  • file:///profiles/
/node_modules/dsh-reload/lib/skill-provider.js
  • file:///profiles/
/node_modules/dsh-reload/lib/skill-reload.js
No change in the tool list.
Elapsed 11ms

— dsh-reload v0.1.2          ← the version stamp changed, so the new code is live

Behaviour:

  • Only the target plugin's own modules are cleared — files under its package root, excluding nested node_modules. Shared dependencies stay in the cache, so no other plugin is swapped out along the way.
  • apply() runs again, so tools, services, commands and prompt sections are all updated.
  • If any step fails, the module cache is restored and the old modules are initialised again, and the report says so, instead of leaving the plugin half-loaded.
  • If the row exists but has no active fiber (it never started, or its startup failed), the call activates it rather than reloading it; a disabled: true row is reported, not force-enabled.
  • If the plugin has a dsh.client half, the report notes that the browser half still needs one page refresh.
  • The tool-list diff is read through the root context. Reloading dsh-reload disposes its own ctx, so reading through it would report every tool as removed.

The skill report

reload_skill states whether the invalidation succeeded, the total number of skills and whether that observation was complete, the added / removed / changed counts against the snapshot taken before the refresh, and the current catalog with each skill's source, provider and file path. When a provider is failing — or when the skill you asked about is not in the catalog — it also finds the skill on disk (same root rules), reports frontmatter problems (missing, not kebab-case, name not matching the directory), lists the roots it scanned, and calls each provider's list() individually to show which one throws. That last part is the only way to see a provider that fails as a whole: the registry skips such a provider and marks the observation incomplete, without telling the model.

Fallback skill provider

With fallbackRoots: true (the default) the plugin registers its own skill provider. It scans the same roots by the same rules but tolerates failures per root: an unreadable root loses only that root's skills, the rest still appear. It registers at the global layer, and registry lookup prefers the nearest layer, so while the host's own provider works it wins and skills are not duplicated; when the host provider fails, the fallback is the only source.

Roots, in priority order (identical real paths are de-duplicated):

SourcePath
cwd-dsh/.dsh/skills
cwd-agents/.agents/skills
project-dsh / project-agentsthe same two directories under the nearest .git ancestor
customcustomSkillDirs
user-dsh$DSH_HOME/skills (default ~/.dsh/skills, .system skipped)
user-agents$DSH_AGENTS_HOME/skills (default ~/.agents/skills)

One deliberate difference from the host rules: the host uses only the nearest .git ancestor as the project root. Where $HOME is itself a git repository, every ~/project/... session gets $HOME as its project root and /.dsh/skills can never be scanned. The fallback provider additionally treats the session working directory itself as a root, at the highest priority; set includeProjectRoots: false to turn that off.

By default the roots are fingerprinted every 2 s and invalidate() is called when a fingerprint changes, so a newly added skill appears without a tool call.

Configuration

- insert:
    - id: tool-reload
      name: 'dsh-reload'
      config:
        fallbackRoots: true       # register the fallback skill provider
        customSkillDirs: []       # extra skill roots
        watchIntervalMs: 2000     # fallback provider poll interval; 0 = no polling
        includeProjectRoots: true # scan /.dsh/skills and the project root
KeyDefaultMeaning
fallbackRootstrueRegister the fallback filesystem skill provider. With false a provider is still registered — the plugin needs the invalidate() handle that comes with the registration — but it lists nothing.
customSkillDirs[]Extra skill roots, scanned after the project roots and before the user roots.
watchIntervalMs2000Poll interval for the fallback provider's roots; 0 disables polling. Values below 250 ms are clamped to 250 ms.
includeProjectRootstrueScan /.dsh/skills, /.agents/skills and the equivalents under the .git ancestor.

The shipped cordis.patch.yml sets the first three; includeProjectRoots defaults to true and only needs writing when you want it off.