dsh-plugin-debugging
Let your DSH agent inspect and fix its own runtime
让 DSH agent 自检自修它身处的运行时
Per-version changes → Releases
You changed something and it didn't take effect? In DSH, that usually means one of three things:
- Config: re-read on every change, so edits apply immediately. This presumes
hmris mounted inprofile; withouthmr, every change degrades to restart-required. - Host code: a process loads code once at startup, so edits need a restart.
- Front-end assets: the browser swaps assets by fingerprint, so edits need a hot reload or refresh.
This plugin lets the agent decide which case you're in, and what to do next.
What it does
After installation, the agent gains a set of tools and a skill document. The skill loads on demand.
Tools
| Tool | Required | Optional | What it does |
|---|---|---|---|
dsh_check_effect_mode |
change: config / bundle-added / host-code / client-code |
alreadyInDependencies, dshHome |
Tell whether a change takes effect live, needs a restart, or needs a hot reload. It probes the running profile instead of guessing. |
dsh_diagnose_plugin_load |
— | package, port, dshHome |
Check whether a plugin actually loaded: bundle membership, mount form, loader tree, with observed evidence kept separate from inferred evidence. |
dsh_list_instances |
— | port |
List the DSH hosts running on this machine. A port counts as DSH only when an unauthenticated GET / returns the exact DSH body. "Could not probe" and "not DSH" are reported separately. |
dsh_check_auth |
— | host, port |
Tell "not running" apart from "not authenticated". |
dsh_dump_config |
— | profile, dshHome |
Show what a profile resolved to: the composed tree, plus the diagnostics that explain it. |
dsh_hot_reload |
one of entryId / module / serverName |
dryRun |
Swap a mounted entry to the latest code on disk without restarting. Rolled back on failure. |
dsh_sign_cookie |
— | host, port, maxAgeDays, dshHome |
Sign a browser-session cookie so a script or CLI client can reach your own DSH instance. |
dsh_restart |
reason |
delayMs, dryRun, force |
Restart this host when a change truly requires it. Off by default. |
Three common traps:
- A
401fromdsh_check_authdoes not mean the session expired. A request carrying nocookiealways gets401, so anauth probe's401only proves the service is running and wants authentication. To tell "the session is still valid" apart from "theauthorityis wrong", you must open thathost:portin a browser. An oldcookiekeeps working after a restart on the same port; only a changed port produces401. dsh_hot_reloadmay leak. Resources the framework does not track — atimerorsocket, for example — leak silently after a swap.dsh_restarthas its own section below.
The skill document behind it
The skill document lives at skills/dsh-plugin-debugging/SKILL.md and carries the full reasoning behind install forms, credential acquisition, process identification, and isolated instances.
The tools are accelerators; if a tool's output conflicts with the skill document, the skill document wins.
Install
Install from the Plugins page:
Open the Plugins page, click Add plugin in the upper-right corner, enter dsh-plugin-debugging, and install.
Install from npm:
dsh plugin --profile web add dsh-plugin-debugging
Install from source:
git clone https://github.com/dsh-plugin-lab/dsh-plugin-debugging.git
cd dsh-plugin-debugging
pnpm install && pnpm run build
dsh plugin --profile web add "$PWD"
Notes:
- Do not skip
pnpm install: with alink:install, dependencies come from the repository itself; skipping it makes the plugin show up as not running and reportERR_MODULE_NOT_FOUND. - Restart to take effect: restart
dsh --profile webafter installing. link:anddsh.bundleare two separate things:link:only decides whether editing source needs a reinstall; whether thepatchlayer applies depends on whether the package declaresdsh.bundle.
Requirements:
Node >= 20, and a DSH version that satisfies the range in package.json.
Maintenance rules:
The profile's dependency and bundle list are managed by dsh plugin add; do not edit them by hand. Do not create packages under $DSH_HOME, and do not run a package manager inside a profile directory.
Safety and boundaries
These tools never restart a process, never rewrite a profile, and never write to host data. Tests enforce this rule: as soon as any write targets a host-data path (profiles / sessions / storages / logs / cache / attachments / backups / integrations / mcp / settings.yaml / .credentials.yaml …), or uses DSH_HOME itself as the target, pnpm run test fails.
The one exception is this plugin's own namespace, $DSH_HOME/<package name>/: only the first directory directly under $DSH_HOME must exactly match the package name; anything below that is unrestricted.
Per-tool boundaries:
- Read-only, no requests:
dsh_check_effect_mode,dsh_diagnose_plugin_load,dsh_dump_config. They only read files the instance already has, and they produce no credentials. - Read-only, sends HTTP:
dsh_check_authconnects to thehost:portyou name;dsh_list_instancesprobes loopback ports only. Neither sends credentials. - Touches the running module tree only:
dsh_hot_reload, and kills no process. - Reads the local signing key:
dsh_sign_cookie, to sign acookiefor your own instance. It sends no request, and thecookievalue is deliberately not returned.
dsh_restart
dsh_restart is the only irreversible action: it ends the host you are debugging. It is off by default, and running it means passing three gates:
- Four deterministic checks: whether it is in a restart loop, whether other sessions or jobs are busy, whether the
bundletable is readable, and whether the launch command can be determined.forceoverrides only the "sessions busy" gate; the other three cannot be bypassed. - A separate approval check: the approval policy is read first, and the model is asked only when the policy is
ask.nevermeans prompts are disabled, so asking underneverwould be refused; the policy must be read before deciding whether to ask. - A separate check before writing
marker: this plugin's own namespace is outside the default file sandbox, so the write explicitly usesdanger-full-access.
All three results are written into the tool output. dryRun runs every gate but writes nothing.
What happens after a restart:
- The new host reads the
marker, wakes every session listed in it, sends one notice, and runs each of them for a turn. - The
reasonin the notice is delivered only to the session that requested the restart. - Other interrupted sessions receive the same facts — when it was requested, how long it took, what became of the
marker— but cannot see thereason.
Platform support
Port-based process identification works on Windows, Linux, and macOS. Specifically:
- When no usable command is found: it reports "could not check", not "no listener". Those are opposite conclusions.
- Weaker fallbacks: they may find "someone is listening" without identifying the process; that is reported as unidentified, not as "no listener".
- The one exception:
dsh_restartcan truly relaunch the host only on Windows (the task-scheduler route has been measured). On other platforms it refuses before writing anything, so it will not leave amarkerbehind and then discover it cannot bring the host back.
Configuration
Fields
These fields configure this plugin in the profile composition. enableRestart also has a switch on this plugin's card on the Plugins page.
| Field | Default | Meaning |
|---|---|---|
enableRestart |
false |
Whether dsh_restart is allowed to run. Takes effect immediately, with no host restart. |
dshHome |
$DSH_HOME, otherwise <home>/.dsh |
Where the instance keeps profiles, logs, and credentials. |
profile |
$DSH_PROFILE, otherwise the default profile |
Which profile the tools inspect. |
port |
0 |
Host port to probe; 0 means find the listening port automatically. |
timeoutMs |
60000 |
Timeout budget for quick work: reading a file, running one command, or one HTTP probe. |
schemaDumpTimeoutMs |
240000 |
Timeout budget for running --dump-config-schema (measured 10.2–15.7 s; 32 s under load). |
Budgets
Inside a tool, every external command or HTTP probe gets a timeout 2 seconds shorter than the tool's total budget. The transport layer must time out before the host's tool deadline; otherwise the agent receives a bare TimeoutError with no error kind, and cannot tell whether retrying is useful.
Debugging another instance
When you need it: you are debugging one instance, but the evidence is on another — a second profile, or another DSH_HOME. An isolated instance must be given a template profile; otherwise the new home has no profile to start with and reports that the profile does not exist.
Which tools take dshHome: dsh_dump_config, dsh_check_effect_mode, dsh_diagnose_plugin_load, dsh_sign_cookie. Both path resolution and the spawned dsh subprocess use the home you pass in, and the output says which one was actually read (dshHomeSource: tool-argument). A value passed as an argument is never silently reported as the configured default.
Which do not, and why:
dsh_check_auth: it answers about ahost:port's authentication state, and already hashost/port.dsh_list_instances: it only does discovery, and all it can give is ahost:port.dsh_hot_reload: it swaps modules in the current process's memory, which cannot cross to another instance.dsh_restart: it kills the host it runs in, so there is no "other instance" it could be aimed at.
One trap: discovery tools can only give a host:port, and nothing maps that to a corresponding DSH_HOME; so you must supply dshHome yourself. Accordingly, dsh_diagnose_plugin_load splits its answer in two: disk evidence comes from the home you passed in, and the loader tree half reflects the current process only.
License
MIT.
Contributing
Bugs and requests go to the repository Issues.
Maintainer's document map → AGENTS.md.
No comments yet. Be the first to write one.