xu-kai-quan
dsh-tool-diagnose
No description
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-tool-diagnose
A DeepSeek Harness Cordis plugin (not a
Skill — see Plugin vs Skill) that exposes a model-facing diagnose tool backed
by an extensible check registry, ctx.diagnostics. Where a symptom-table Skill tells the agent
which shell command to run and how to read its output, this plugin runs the introspection itself,
inside the process, against live Cordis state — and lets other plugins contribute more checks the
same way this package's own checks.ts does.
Install
If you use the DSH web UI:
dsh plugin --profile web add dsh-tool-diagnose
If you run one-off tasks from the terminal instead (dsh --profile headless "some task"):
dsh plugin --profile headless add dsh-tool-diagnose
web and headless are the two names DSH ships out of the box, matching the two commands above —
whichever one you already use day to day, use that same word in place of --profile web /
--profile headless. You don't need to set anything up first; both initialize themselves
automatically the first time you use them.
Using a custom profile name instead of web/headless?
A "profile" is just a folder under $DSH_HOME/profiles/<name>/ ($DSH_HOME defaults to
~/.dsh). If you've set up your own named profile, use that folder's name after --profile. See
what you already have:
ls "$DSH_HOME/profiles" # macOS/Linux
dir $env:DSH_HOME\profiles # PowerShell
Then, in a session: "Use the diagnose tool to check why the bash_run tool isn't showing up." See Status for the GitHub-source install alternative, and Try it locally for hacking on a checkout without installing it into a profile.
Layout
src/
registry.ts # ctx.diagnostics: DiagnosticRegistry Service — registerCheck/list/run
checks.ts # create*Check FACTORIES (2 always-on) + optional checks (4, service-gated)
index.ts # the plugin: mounts the registry, calls each factory, registers the `diagnose` tool
tests/
registry.spec.ts # registry mechanics + the closure-vs-passed-ctx regression test
checks.spec.ts # credential-format / approval-policy branches + the index.ts wiring regression test
checks-integration.spec.ts # all 6 checks against real Loader/ToolRuntime/SessionStore/etc.
cordis.patch.yml # the bundle layer a profile applies when it lists this package
package.json # declares dsh.bundle, pointing at cordis.patch.yml
What the checks actually do
Every one reads a real, already-shipped service — no invented APIs. Two are always registered
(they only need ctx.tools, which this plugin already requires, or nothing beyond ctx.diagnostics
itself); four more register only when their service is actually mounted in this deployment (see
Optional checks).
plugin-fiber-statereadsctx.loader.entries()— the same primitive@deepseek-ai/dsh-host-plugin-inventoryprojects to the web UI — and reports each matching entry'senabled/disabledstate and fiber phase (pending/loading/active/failed/unloading/none). This is the live version of manually runningdsh --profile <name> --dump-configand reading it by eye. (It's gated onctx.loaderlike the other optional checks below, even though the description here groups it with the "always on" pair for token-cost purposes — seeindex.ts.)tool-visibilitycallsctx.tools.get(name)to tell you whether a tool is registered and visible in the global scope, or absent (never registered, owning plugin failed to load, or scope-restricted away).
Call the tool with an optional target (check-specific: a plugin module-name substring, a tool
name, a credential-reference name, or a session id); omit it to get every check's unfiltered view.
Optional checks (service-gated)
Each depends on a service dsh-base normally provides but an unusual deployment might not mount;
index.ts registers each one inside its own ctx.inject([...]), so a deployment missing one
service still gets every other check instead of the whole plugin failing to load.
credential-resolution(needsctx.credentials) — resolves a credential reference (e.g.DEEPSEEK_API_KEY) viactx.credentials.describe(): configured/source/writable, never the value itself, per that seam's own doctrine.approval-policy(needsctx.approval,ctx.sessions) — reports the deployment default approval policy ("ask"/"never"), or a specific session's override when given a session-id target.token-pressure(needsctx.tokenMeter,ctx.sessions) — measures a live session's currenttotalTokens(request/response pressure) vs.surfaceTokens(fixed-heuristic total), useful for "why hasn't compaction run yet" symptoms.subagent-tree(needsctx.subagents) — flattens a session's descendant subagent tree, reporting count, max depth, and any descendant the registry couldn't interpret (corrupt/unavailable).
Extending it: a third-party check
Any other plugin can depend on this one and register more checks. A check is a factory that
closes over its own already-scoped ctx — construct it inside the ctx.inject callback for
whatever services it needs, not as a standalone object built ahead of time (see
Audit notes for exactly why that distinction is load-bearing):
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-extra-diagnostics'
export function apply(ctx: Context) {
ctx.inject(['diagnostics' /* , 'whateverServiceThisCheckNeeds' */], (ctx) => {
ctx.diagnostics.registerCheck({
id: 'my-check',
description: 'What this check looks at and what target means for it.',
run(target) {
// read ctx (closed over above) here — never accept ctx as a run() parameter
return []
},
})
})
}
Disposing that plugin's fiber automatically unregisters its check — registerCheck returns the
disposer but you rarely need to call it directly; Cordis calls it for you on unload.
Try it locally
For hacking on a checkout rather than installing the published package (see Install above for that):
pnpm dsh plugin --profile demo add /path/to/dsh-tool-diagnose
dsh --profile demo --dump-config # confirm the "diagnose-tool" row is present
dsh --profile demo
On Windows, if you instead point a --patch overlay directly at src/index.ts (the
tutorial-style local-file pattern) for a quick source-launch test, the module name must be a
file:/// URL, not a bare D:/... path — Node's ESM loader rejects the latter with
ERR_UNSUPPORTED_ESM_URL_SCHEME.
Plugin vs Skill
If you want a portable, no-build, copy-into-~/.agents/skills/ symptom table any DSH session can
read instead, that's a Skill, not a plugin — see the original share in
Discussion #1739, which is what
this plugin took its methodology cues from (especially version-anchoring knowledge to a specific
dsh release). The two compose well: a Skill can tell the agent "call the diagnose tool first,
then read its findings against this table" instead of asking it to run shell commands and parse
the output itself.
Status
- Published on GitHub, public, tagged with the
dsh-plugintopic. -
LICENSEfilled in with a real copyright holder. - Published on npm as
dsh-tool-diagnose— see Install above. Installing straight from GitHub source instead (dsh plugin --profile <name> add github:xu-kai-quan/dsh-tool-diagnose, see the build-script caveat below) still works too, and tracksmasterrather than the last tagged npm release.
If you fork or extend this:
- Confirm dependency versions against what you actually build/test against — this skeleton pins
the versions available at scaffold time:
@deepseek-ai/cordis@4.0.1,@deepseek-ai/cordis-plugin-loader@1.0.2,@deepseek-ai/schemastery@3.18.1, and@deepseek-ai/dsh-tools/dsh-credentials/dsh-user-approval/dsh-token-meter/dsh-session/dsh-subagentall at0.1.0-rc.5(the dsh family is versioned in lockstep). - Ship prebuilt output (
npm publish, orpnpm pack+ tarball) rather than relying on installers allowing a git-installpreparescript, unless you want users to opt into that. npm install && npm test && npm run buildbefore tagging a release — and, per the audit notes below, actually boot it inside a realdshprocess at least once. This package's own bugs were invisible to its test suite until that step.- Write real checks — the six shipped here are a useful starting set, not the ceiling; port
whatever mechanism chains you'd otherwise write as Skill symptom rows into
DiagnosticChecks that actually read the live state instead of describing how to. Grep the target package's README for itsctx.<key>service surface first, the way this package's checks were built.
Audit notes
Two real bugs a full test suite (22 passing tests) did not catch
Both were found only by actually running this exact plugin inside a real dsh --profile headless
process and asking the agent to call the diagnose tool for real — every unit and integration
test up to that point passed cleanly while the tool was either unregistered or broken.
- Wrong
output.schemaDSL fordefineTool.dsh-tools'ValueSchemaSpecmarks a property optional/required withrequired: trueon the property itself, mirroringparameters— NOT a sibling plain-JSON-Schemarequired: [...]array on the object node. The wrong form compiled fine undertscand looked identical to the (correct)parametersblock a few lines above it, but threwJsonSchemaError: schema.items.required is not supported by the value schema DSLthe instantdefineTool()ran — which happens at plugin-load time, so the entirediagnosetool silently failed to register with no crash and no log line, in every profile. - A ctx reference does not survive being stored and handed back through another service's
this.ctx. The original registry design capturedctx(orthis.ctx) once and passed it to each check'srun(ctx, target)later. Verified against the real@deepseek-ai/cordis4.0.1 runtime with isolated repro scripts: a scope's injected-service access (ctx.approval,ctx.tools, even a staticallyinject-declared dependency) is granted per exact context reference and does not propagate when that reference is stored as a plain value inside anotherServiceinstance and read back viathis.ctxin a later method call — it throwscannot get property "X" without inject, regardless of whether the original grant was static or from a nestedctx.inject(). The fix:DiagnosticCheck.runtakes noctxparameter at all; every check is a factory (create*Check(ctx)) that closes over its scope'sctxdirectly at construction time, and the registry only ever callscheck.run(target).
tests/registry.spec.ts and tests/checks.spec.ts both now carry regression tests for bug #2
(a check registered from a deeper scope than run() is later called from); nothing in the suite
directly regression-tests bug #1 (a wrong-but-type-checking output.schema shape) — if you touch
that schema, boot the plugin for real again rather than trusting npm test alone.
What was verified this way, end-to-end, with a live model call
Against a real dsh --profile headless boot (no mocks, no fakes, real npm-installed
dependencies): the diagnose tool appears in the model's tool list; called with no target, it
returns real findings from plugin-fiber-state (4 real disabled-plugin rows: cordis-plugin-hmr,
dsh-bash-sandbox, dsh-tool-bash, dsh-skill-badge) and approval-policy ("ask", the real
deployment default); called with target: 'DEEPSEEK_API_KEY', credential-resolution correctly
reported it configured (source: file, writable) without leaking the value; called with
target: 'write', tool-visibility correctly reported it registered and visible. credentials,
approval, sessions, tokenMeter, and subagents all turned out to be mounted in the
headless profile — only loader gating was ever a real portability concern, and it's now
handled the same way as the other four.
Per-check unit-test technique, in case you extend these further
plugin-fiber-state:ctx.plugin(Loader)+ctx.loader.builtins.<name> = fn+ctx.loader.create({ name })— the same harnessdsh-host-plugin-inventory's own test suite uses, avoiding any real file/module resolution.tool-visibility: directnew ToolRuntime(ctx)— but its constructor readsctx.systemPromptsynchronously, soawait ctx.plugin(SystemPrompt, {})has to happen first, or construction throwsCannot read properties of undefined (reading 'tools').dsh-tools' owncode-mode.spec.tshas the same prerequisite.token-pressure/approval-policy: directnew SessionStore(ctx)+new TokenMeter(ctx)/new ApprovalService(ctx, config), thenctx.sessions.create()for a live session — no persistence or agent loop needed sincemeasure()andoverrideOf()take the session object directly rather than reading it back through a service.subagent-tree: directnew SessionStore(ctx)+new SessionProjectionRegistry(ctx)+new SubagentRuntime(ctx).listDescendants()throwsSubagentErrorbefore any candidate scan ifsessionProjectionsisn't mounted — even for a session with zero children — so that service is not skippable even for the trivial empty-tree case tested here.
A real dependency-declaration bug, separately caught and fixed
@deepseek-ai/dsh-credentials was briefly marked optional in peerDependenciesMeta, but
checks.ts does a hard value-import of credentialRef from it — so the module import would fail
at load time regardless of whether the credential check is ever registered, contradicting
"optional". dsh-user-approval, dsh-token-meter, and dsh-subagent are genuinely optional:
they're referenced only via import type {} side-effect imports, which TypeScript erases, leaving
no runtime import to fail if the package is absent.
Still not covered
The non-trivial branches of subagent-tree (an actual descendant, or a corrupt/unavailable
diagnostic entry) aren't tested — reaching them needs a real subagent provider and a started child
(see packages/subagent/subagent/tests/list-children.spec.ts in the harness repo for the full
weight of that harness: JSONL persistence, a mock LLM adapter, a real spawn/fork provider). The
trivial empty-tree path is covered, and was independently confirmed by the live boot test above.