dsh-cc-plugins
Claude Code feature-parity plugins for the DeepSeek Harness (dsh) — settings cascade, permission rules, hooks, memory, skills, subagents, and a CC Mode agent preset, installable into any dsh profile.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 18, 2026
Introduction
dsh-cc-plugins
Claude Code feature-parity plugins for the DeepSeek Harness (dsh), shipped as an out-of-repo plugin set that any dsh installation loads through its profile system.
Layout
packages/
settings/settings-cascade 5-level settings file precedence (~ enterprise/user/project/local/flags)
settings/settings-migrations version-gated settings.json migrations on mount (mechanism ready; registry empty until the first format change)
interaction/permission-rules allow/deny/ask rule engine + mode state (CC /permissions semantics)
interaction/command-status /status
interaction/command-doctor /doctor
interaction/command-memory /memory — list/read CLAUDE-code-style memories
interaction/command-skills /skills — list installed skills
interaction/command-help /help — usage help
interaction/command-config /config — inspect/set settings
interaction/command-permissions /permissions — allow/deny/ask rules + mode
interaction/command-version /version — show product/version info
interaction/command-release-notes /release-notes — view release notes
interaction/command-diff /diff — diff CLAUDE.md / settings
interaction/command-init /init — scan project and scaffold CLAUDE.md
interaction/command-plugin /plugin — manage plugins
interaction/command-mcp /mcp — manage MCP server connections
interaction/command-tasks /tasks — show open tasks / todo
interaction/command-resume /resume — resume an interrupted session
interaction/command-branch /branch — worktree branch management
mcp/mcp-client MCP client with OAuth 2.1 + resources + prompts (vendored superset)
mcp/mcp-config `.mcp.json` parser → mcp-client registrations (library)
hooks/hook-protocol hook wire protocol incl. http executor (vendored superset)
hooks/hooks-claude-code CC hook bridge — 18 of 30 events (command + http executors)
core/tools vendored tool registry + reserve()/isAdmitted() (deferred names)
core/tool-search ToolSearch tool + DeferredToolRegistry
core/tool-sleep Sleep tool (cooperative interrupt; CC SleepTool parity)
core/tool-structured-output StructuredOutput tool factory (CC SyntheticOutputTool parity)
core/tool-notebook-edit NotebookEdit tool (fs-seam .ipynb edits + read-before-write gate)
skill/skill-claude-code SKILL.md provider reading CC dirs; CC paths conditional activation + bundled subset (debug/simplify/batch)
preset/claude-code-agents `.claude/agents` → subagent providers (library)
preset/cc CC Mode agent preset (composition-only: agent.cordis.yml + preset.yml)
compat/cc-plugin-loader mount a CC plugin directory (plugin.json) onto dsh seams (library)
compat/cc-model-aliases CC frontmatter model aliases → {provider, model} routes (library; wired by cc-shell)
compat/cc-output-styles CLAUDE.md output styles → system prompt
memory/memory CLAUDE.md memories + recall (recentTools suppression) + opt-in team memory
memory/memory-consolidation background memory consolidation
workspace/tool-git-worktree EnterWorktree / ExitWorktree tools
subagent/coordinator coordinator mode (delegation-only agent surface)
compaction/compaction-micro model-free stale-result microcompaction
session/command-cost|export|stats /cost /export /stats
bundle/cc-permissions profile bundle: settings-cascade + permission-rules
bundle/cc-shell profile bundle: everything else, plus the on-disk glue plugin (also mounts `dsh-tool-ask-user` AskUserQuestion over the base-owned `dsh-user-questions` seam and `dsh-schedule`)
test-support/agent-loop-mock vendored test fixture (not a plugin)
Install (half a minute)
Prereq: a dsh CLI installation (dsh on PATH, version ≥ 0.1.0-rc.5).
# pick a profile name (created on first use); rc.6 add syntax:
dsh plugin --profile cc add <this-repo> # or the published npm names / file: links
# compose: bundles are hoisted into the profile and listed in dsh.profile.bundles *ahead* of
# your own patch file, their roofs sorted before your cordis.patch.yml.
dsh --profile cc "your task"
dsh plugin forwards to pnpm in $DSH_HOME/profiles/cc and auto-registers every
installed package that declares dsh.bundle into dsh.profile.bundles (append on add,
drop on remove). Hoisted pnpm linking puts the whole plugin tree flat, while every
in-box dsh package (peer deps like @deepseek-ai/cordis) resolves through the
installer-maintained $DSH_HOME/profiles/node_modules symlink fallback — external
plugins always share the installation's single cordis instance.
Recommended order in ~/.dsh/profiles/cc/package.json:
{
"dependencies": {
"@jianxx/dsh-cc-bundle-permissions": "...",
"@jianxx/dsh-cc-bundle-shell": "..."
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@jianxx/dsh-cc-bundle-permissions",
"@jianxx/dsh-cc-bundle-shell"
]
}
}
}
Your own tweaks land in ~/.dsh/profiles/cc/cordis.patch.yml (applied after every bundle).
Local development without publishing
To test unpublished changes against a real profile instead of publishing first:
pnpm run build # emit lib/ per package
bash scripts/sync-local-profile.sh web # flat-copy @jianxx/* into the profile
dsh web # profile bundles registration already in place, boots to a UI
scripts/sync-local-profile.sh copies (not symlinks) the built packages into the
profile so every @deepseek-ai/* import resolves through the installation's single
cordis instance — the same way a published bundle does. Re-run it after every build.
On a network-restricted host,
pnpm installstalls fetching per-package registry attestations even with--offline; seedocs/dev.mdfor the offline recovery.
CC Mode preset
CC Mode is dsh's fifth agent preset (the four built-in modes are unchanged — see below). It exposes the full CC-parity surface from this repo as a single selectable preset.
Install in two steps:
bash scripts/sync-local-profile.sh web # install the @jianxx/* packages into the profile
bash scripts/sync-cc-preset.sh # install the CC preset combo into ~/.dsh/.agent-presets/cc
The second script rsyncs packages/preset/cc/agent.cordis.yml and
packages/preset/cc/preset.yml into ~/.dsh/.agent-presets/cc (respecting
$DSH_HOME). Re-run it whenever those files change, then restart dsh — the
preset list is re-scanned at the next boot.
Select the preset either through the web UI's preset selector, or by setting
agent-presets.default="cc" in settings. To uninstall, delete the
~/.dsh/.agent-presets/cc directory.
Model aliases
CC agent frontmatter names models by alias (model: opus, model: sonnet,
model: inherit). The cc-shell glue resolves those aliases per spawn against
deployment defaults (modelAliases on the glue row) overlaid by the live
model-aliases settings namespace (user/project/local layering, null
deletes a configured entry). Unconfigured builtin aliases and inherit fall
back to the parent route; anything else passes through as a literal model id.
See packages/compat/cc-model-aliases/README.md for the full merge and
resolution semantics.
The four built-in modes are behaviorally unchanged: the host plane keeps only the tools-registry fork, the five-level settings cascade + permission rules, and settings migrations — none of which produces a visible change on the stock modes.
How the loading works (the mechanism our names rely on)
- Bundles list "rows" in
cordis.patch.yml; each row is an entry{id, name, config?, insert?…}the Loader interprets. - The dsh launcher resolves each bundle's
nametwo-anchor: the dsh installation first, then the profile directory. This is why our packages use the@jianxxscope: a@deepseek-ai/dsh-*name would be shadowed by the in-box copy. - Inside a patch, each
name:is resolved from the profile directory as base URL; hoisted node_modules carries the whole bundle dependency tree, so one profile dependency on a bundle pulls every plugin with it. - Plugins' peers (
@deepseek-ai/cordis, service-definition packages like@deepseek-ai/dsh-invariants) are NOT bundled: they resolve via the installation-wide symlink fallback, keeping one cordis instance per process. Never ship them as ourdependencies.
Vendored packages (upstream + delta)
Four packages vendor upstream dsh packages plus our changes, because the deltas are invasive (not wrappable):
| package | delta | why vendored |
|---|---|---|
core/tools | +reserve/isAdmitted + reserved-name table | extension-point methods on the Service Provider; free functions cannot see the private layer tables |
mcp/mcp-client | +OAuth 2.1 flows, +resources/prompts surfaces | same-module internal plumbing throughout the client |
hooks/hook-protocol | +http executor + dispatch options | wire-protocol module shape |
hooks/hooks-claude-code | full CC event/executor bridge | only exists at all through the fork's expansion |
At runtime each sits beside its upstream peer under a different npm name. Where service identity matters, the bundle patch disables the in-box row by id and remounts ours under a unique id:
- id: tools
disabled: true
- insert:
- id: tools-cc
name: "@jianxx/dsh-cc-tools"
The disable marker targets the base row by its id, and the remount registers under a distinct id, because cordis-plugin-loader dup-checks every incoming entry (a {id, disabled: true} marker included) — reusing the same id for the insert would throw duplicate loader entry id at mount. Service injection is name-keyed, not row-id-keyed, so the remount resolves the same underlying service. NOTE (upstream TODO): the id-keyed dump/compose path renders this disable+rename pattern fine, so the loader's stricter dup check diverges from it; worth splitting out an issue upstream.
Subscribers type against upstream service types — the vendored runtime is a structural superset; the two nominal ToolExecutionToken brands are bridged by explicit casts at the 6 mixed call sites (see coordinator / hooks-claude-code sources; documented in code) — never routed at runtime because only one tools registration exists per scope.
Known limits (upstream vocabulary boundary)
- dsh session event vocabulary is closed in-repo today (
KNOWN_SESSION_EVENT_TYPES, andSession.appendonly accepts envelope options for surface events). Out-of-repo plugins cannot log new event types safely: readers that meet an unknown non-ignorable type refuse to replay. Consequences we chose:permission-ruleskeeps per-session mode overrides in memory (a WeakMap keyed by the live Session); a resumed session starts back at the deployment default mode. (The fork wrote a durablepermission/modeevent.)compaction-microno longer appends its log-only decision record; the replacement nodes already carry the deterministic marker, so decisions still reconstruct from replay + code.
- Track: ask upstream for either ignorable-aware
Session.appendor an event-registration surface; restore the durable records then. hooks-codexandtool-cordisfork deltas were NOT moved: they were generated-catalog/type-hygiene noise with no behavioral need on top of upstream.tool-web-ccmounts a fetch-capable web tool at the host plane (fetch: true, 60s search timeout), carrying the basetool-webrow's caps. Becausedsh-web-appdeliberately movestool-webbehind agent presets and disables the base row, this host-plane insert intentionally bypasses that scoping for CC parity — watch for a duplicate/missing web tool in a UI pass.- The LSP trio (
dsh-lsp/dsh-lsp-stdio/dsh-tool-lsp) and theweb-fetch-httpexecutor are NOT shipped by the CLI dependency tree through rc.6, so their rows cannot resolve from the installation and are removed frombundle/cc-shell. Re-add them once the installation carries them (they are cordis-peer packages; version-skew untested). user-questionsis owned bydsh-base(it has been since before rc.5), so the bundle no longer inserts it;tool-ask-userconsumes the base-owned seam. A missing UI provider yields a gracefulNO_PROVIDERtool error.- On the WEB profile the native
/export(a browser download stub fromdsh-web-app's session-log-download row) holds the name; our file-writing/exportregisters only where the name is free (CLI profiles). The plugin skips registration when/exportis already registered. - hooks
configPathdefaults to$DSH_HOME/hooks.json(resolved viactx.dshHomePath); per-session project-local hook discovery remains a TODO. /costships an empty price table by default (modelTable: []in the bundle row). The schema stays strict because composition decides pricing — deployments must supply pricing where needed.- Schedule (
dsh-schedule) is session-local only: one-shotafter_secondsdelays, absoluteattargets, and fixed-rateevery_seconds(≥300s). Claude Code cron-expression parity is deferred upstream.
Develop
pnpm install --frozen-lockfile # offline-friendly: everything pins to the local dsh checkout via link:
pnpm run typecheck # tsc -b (emits lib/ per package)
pnpm test # vitest
Upstream types resolve through link: devDeps into a sibling dsh checkout at
../deepseek-harness (built once with pnpm run build there). To publish for real,
replace link: devDeps with released version ranges and publish the vendored set under the @jianxx scope.
Working on
pnpm-lock.yamlor dependency declarations on a network-restricted host: seedocs/dev.mdfor what the frozen-lockfile check actually verifies and the test-time dependency declaration contract.