dennisrongo/dsh-plugins

dsh-plugins

Dennis Rongo's plugin collection for DeepSeek Harness (dsh)

待分类ai-agentdeepseek-harnessdsh

dsh-plugins

Dennis Rongo's plugin collection for DeepSeek Harness (dsh) — an MIT agent harness where every capability is a plugin.

Everything here is built on the harness's public seams: cordis service keys, the Typert host/client bridge, shell.* slots, the system-prompt section registry, and ctx.cmdlineArgs. No forks, no patched launcher, no vendored harness code.

Both surfaces are supported. Every plugin runs unchanged under the dsh CLI and under DSH Desktop — a community desktop wrapper that ships the harness pre-packaged with multi-provider model support ("Any model, zero setup"), for Windows and macOS. It is an independent project, not affiliated with DeepSeek.

The desktop keeps its own DSH_HOME (%APPDATA%\dsh-desktop\harness on Windows) with its own profiles, so you install a plugin once per profile on whichever surface you use. Nothing here is CLI-specific: the same package, the same cordis.patch.yml row, and the same /api endpoints serve both.

Status: works with DSH Desktop 0.10.0 (dsh 0.1.7-rc.2) and the dsh CLI at 0.1.5-rc.3 — both surfaces verified live, all six host services answering. Desktop 0.10.0 needs the codec-fix releases of the host plugins (see the table below); older Desktop/CLI versions keep working, since the fix is backward-compatible. dsh is a fast-moving 0.1.x dev preview that promises breaking changes — re-verify against your installed version, and read Harness compatibility for what has broken so far and what is still open.

Platforms: the plugins are plain Node and platform-agnostic, and DSH Desktop ships for Windows and macOS, so they should run on either. The tooling is portable — verify.mjs, anchor.mjs, link-superpowers-skills.mjs are plain Node, and profile live-linking has a script per platform (dev-link.ps1 on Windows, dev-link.sh on macOS/Linux) behind one dev-link.mjs entry point. Everything here has been exercised on Windows against both the CLI and DSH Desktop; macOS and Linux are untested.

Harness compatibility

Verified against
DSH Desktop0.10.0
dsh (harness)0.1.7-rc.2 — the copy DSH Desktop 0.10.0 bundles
dsh (CLI, global)0.1.5-rc.3 — what npm i -g @deepseek-ai/dsh resolves to, and what anchor.mjs links against

Desktop 0.10.0 requires the codec-fix releases of the six host plugins: @dennisrongo/dsh-git ≥ 0.5.1, dsh-hooks ≥ 0.2.1, dsh-memory ≥ 0.1.3, dsh-mission-control ≥ 0.5.2, dsh-plan-board ≥ 0.3.2, dsh-todo ≥ 0.5.3. Older releases of those six fail to boot on Desktop 0.10.0 (see the 2026-10-03 entry); older Desktop and CLI versions are unaffected either way, because the fixed codecs carry both era's members.

Older harnesses are not pinned or tested. Because dsh is a 0.1.x preview, a plugin here is expected to break on upgrade rather than warned about in advance; what follows is the running record, newest first.

What broke, and when

2026-10-03 — DSH Desktop 0.10.0 (dsh 0.1.7-rc.2). Two changes, one of them ours:

  1. Every host service refused to register, and the harness refused to boot. dsh-typert-loader 0.1.7 changed the strict-codec contract: a codec is no longer a schema (a zod v4 instance the loader validates and the gateway parses through) but a create() factory returning the schema, which requireStrictCodec demands as a function and the gateway calls as codec.create().parse(value) (dsh-api-gateway, decode()). All six host manifests here predate that, so validateTypertManifest threw for the first invocation of each, auditStartupEntries failed the entry, and the host exited before serving anything:

    typert-loader: @dennisrongo/dsh-todo invocation
    "@dennisrongo/dsh-todo#dshTodo/list" parameter codec has no create() factory
    … 6 typert contributor(s) failed to register
    [desktop] Harness entry failed during startup; stopping immediately
    

    The fix carries both members — schema for dsh ≤0.1.5 (whose loader requires a zod instance and whose gateway parses codec.schema.parse(...)) and create: () => schema for 0.1.7+ — in the one descriptor() builder each plugin's src/remote.ts shares between its host and client faces. Neither era's validator rejects the other's member, so one build serves both surfaces and stays backward-compatible; the CLI at 0.1.5-rc.3 was still booting on schema the whole time. Fixed in dsh-git 0.5.1, dsh-hooks 0.2.1, dsh-memory 0.1.3, dsh-mission-control 0.5.2, dsh-plan-board 0.3.2, dsh-todo 0.5.3 — until those are published, market installs of the older releases still fail to boot on Desktop 0.10.0. Verified by importing every built manifest through the Desktop bundle's own validateTypertManifest, then scratch-booting the bundled harness against a throwaway DSH_HOME with the profile plugins junctioned at this repo: all six services answer ok:true on 0.1.7-rc.2.

  2. Plugin deployment moved to enforced "generations" — dev links no longer reach the Desktop. 0.10.0 migrates profile plugins into profiles/.generations/live/<pkg>+<version>+<hash>/node_modules/<pkg> snapshots, with the profile's node_modules/@dennisrongo/<pkg> symlinked there and dsh.desktop.generationProjection in the profile manifest naming the generation each plugin must use. At every app start a repair ENFORCES that projection, and it owns every layer you might try to repoint — each of these was tried and reverted within one app start:

    • dev-link.ps1 junctions at the profile entry → the repair restores the generation symlink (the junction never survives a restart).
    • dsh plugin add link:<repo> (with the profile's own pnpm 10 via corepack pnpm@10.34.5) rewrites the dependency and symlink correctly — but the CLI does not know generationProjection, so the app reprojets the manifest back to version pins and restores the old generations.
    • Junctioning INSIDE a generation snapshot (the profile symlink chains through it) → the repair re-materialises the generation content itself, replacing the junction with a fresh copy of the recorded build.

    What the repair DOES respect is a generation whose own pnpm lockfile records the install — the existing generations are file: installs of a staged source copy (their node_modules/.pnpm/lock.yaml says so), which pnpm materialises as a copied directory. scripts/desktop-generations.mjs mints generations that way from this repo's builds — a file: install pointing straight at the package, with generation.json carrying the local source path — then wires the ids into the projection, desired.json, the manifest deps/overrides and the node_modules entries, so every layer the repair checks agrees and there is nothing to revert. Verified end to end: the app boots and all six host services answer ok:true. A minted generation is a COPY, so it does not self-deploy — re-run the script (then restart the Desktop) after host-half rebuilds. The dsh CLI surface keeps the old dev-link workflow unchanged.

    For other people's machines none of this applies: they install from the market, which only sees published versions — during this break the profile held dsh-plan-board 0.3.0 while 0.3.1 was already published, so even published fixes wait for the user's update click.

    Also observed: with the CLI at 0.1.5-rc.3 and the Desktop at 0.1.7-rc.2, the anchors point at a dsh two prereleases older than the one that runs them (dev-link.ps1 reports 244 desktop-bundle vs 2 npm-host shared deps). The scratch boot above ran exactly that skew and every service mounted — these plugins are manifest-driven, so the per-copy WeakMap identity concern in dev-link.ps1's header still does not bite. Keep an eye on it anyway: that note predates any real divergence.

    And two tooling fixes rode along: verify.mjs --port now authenticates (--token=<t> from the launch URL, exchanged for the session cookie — a plain fetch loses the 302's Set-Cookie, so the mint reads the redirect response directly), and its /plugins/<pkg>/client.js fetch is gone — dsh-client-modules serves that route only under a revision (?rev=…), so a bare fetch 404s by design and the old check was reporting a defect that never existed.

2026-09-16 — DSH Desktop 0.9.0 (dsh 0.1.5-rc.2). Three separate breaks, two of them ours:

  1. The harness would not boot at all. Not a plugin in this repo: DSH Desktop's own bundled dshmarket imports installSettingsSection and settingsNamespace from @deepseek-ai/dsh-settings, and dsh deleted both (dshmarket's own source dates this to 0.1.2-alpha.1). A missing ESM named export is a SyntaxError at module evaluation, so the loader failed the entry and the host exited 1:

    dsh: plugin tree failed to load: … The requested module
    '@deepseek-ai/dsh-settings' does not provide an export named
    'installSettingsSection'
    

    dshmarket 1.45.1 inlines the helper and fixes it. The catch is that the profile only receives that version once plugin migration completes — and migration was being aborted by the next two items, which left the profile pinned to dshmarket 1.36.0. Anything that blocks migration therefore presents as this crash, whatever the real cause. Read the migration failed, restoring the pre-upgrade profile: <reason> line in %APPDATA%\dsh-desktop\logs\harness.log; the reason is the actual bug.

  2. dsh-skills aborted plugin migration. 0.9.0 validates every generation's required dependencies with a bare require.resolve. @dennisrongo/skills is a bin-only CLI package — no main, no exports, no index.js — so it does not resolve that way and migration failed closed:

    @dennisrongo/dsh-skills failed peer validation: @dennisrongo/skills does not
    resolve from the generation or installation closure
    

    Fixed in dsh-skills 0.1.1 by declaring the catalog an optionalDependency, which matches how the plugin already treats it — it resolves the manifest subpath and falls back to skillsRoot, then DSH_SKILLS_ROOT, then a clone probe. pnpm still installs it, so the catalog ships unchanged.

  3. dsh-plan-board's panel slid underneath dsh-mission-control's rail. The shell slot for the conversation column was renamed conversation → main.conversation. The lookup missed, useDock fell through to its no-column branch, and that branch pinned right: 0 to the viewport — putting the whole panel under the 400px rail instead of beside it. Nothing logged; it only showed up as clipped text. Fixed in dsh-plan-board 0.3.1: the slot name is now a candidate list (newest first), and the fallback honours a frame reservation too, so the next rename degrades to a slightly-wrong width instead of a buried panel.

2026-09-04 — @deepseek-ai/dsh-client-runtime removed from the dsh install. Seven plugins (dsh-git, dsh-memory, dsh-mission-control, dsh-plan-board, dsh-theme, dsh-todo, dsh-weather) import ClientContext from it as a type-only import. anchor.mjs now reports MISS … is not in the dsh install for each, and tsc fails with TS2307, plus TS7006 fallout where parameters lose their inferred types. CI has been red since this date and the last green run was 2026-09-02.

Status: open, and deliberately so. The import is types-only — it is erased at build time, every runtime suite passes, and all eleven packages build and ship correctly. Fixing it means either vendoring the type or dropping to unknown across seven plugins, which is worth doing deliberately rather than under a release. Two knock-on effects are worth knowing:

  • pnpm run typecheck fails locally for the same reason. Compare against a stash before blaming your own change.
  • Anchor symlinks created while the package still existed were left dangling in seven plugins' node_modules/@deepseek-ai/. On Windows that broke DSH Desktop 0.9.0's migration preflight (ENOENT … stat … dsh-client-runtime), which is how it contributed to the boot failure above. Delete them; the current anchor.mjs skips a missing package rather than re-creating the link, so they do not come back.

Plugins

PackagenpmAddsHalvesEndpoints
dsh-todonpmper-workspace sprint/roadmap task list, plus a dsh-todo CLIhost + client + CLIdshTodo/list, replace, scanDigest, readSuggestions
dsh-gitnpmsource-control tab — changes, history, branches, stash, worktreeshost + clientdshGit/status, diff, commitFiles, commitDiff, stage, commit, init, sync, suggestMessage, changeToken, refs, branch, merge, stash, worktree, suggestBranch, stashFiles, stashDiff
dsh-weathernpmweather bar in the shell overlayclient only—
dsh-headless-plusnpm--model / --resume / --continue for the headless appCLI app—
dsh-superpowersnpmSuperpowers methodology as a system-prompt section and a skill provider (bundled, no clone needed), plus a nudge to ask via ask_user_question instead of prosehost—
dsh-skillsnpmthe @dennisrongo/skills library as an installable skill cataloghost—
dsh-mission-controlnpmfleet dashboard overlay — sessions, swarm tree, token burn, permission inbox, pomodoro timerhost + clientdshMissionControl/load, save
dsh-themenpmtwelve themes, eight accents, contrast and scale sliders, and three fonts with two bundled, live previewclient + tiny host—
dsh-hooksnpmClaude Code-compatible hook lifecycle — shell commands at eight lifecycle points, plus contextual skill hints beside the composerhost + clientdshHooks/describe, recent, hints, hintsToken, dismissHint
dsh-plan-boardnpmdurable plans — captures every exit_plan_mode plan to disk, opens a review panel you can approve or revise from, keeps the historyhost + clientdshPlans/list, get, changeToken, discard, pin
dsh-memorynpm/remember into the AGENTS.md hierarchy, plus a tab showing which instruction files the loader kepthost + clientdshMemory/inspect, remember, read

dsh-todo

A task tab scoped to the workspace you're working in, so each project keeps its own backlog instead of one global pile. It is a sprint/roadmap list, not a checklist: each task carries a status, a priority, and optional release and sprint labels.

What you get. A tab beside Chat and Trajectory. Tasks move through real workflow states — backlog · todo · in-progress · blocked · done — changed from a pill on the row, because a boolean can't express the two things a standup actually asks: what is moving, and what is stuck. Priority is P0–P3 as a chip, with only P0/P1 coloured so the list flags what's urgent instead of turning into a rainbow. Release (1.5 or 0.5.1, what ships together) and sprint (24, when it's worked) are deliberately separate numeric axes — a release takes up to three numbers so a patch gets its own label, a sprint is a single decimal, neither admits alpha, and they sort by version semantics (1.10 above 1.9) with no releases table to administer. Group by None · Status · Release · Sprint · Priority gives collapsible sections with their own progress bars; grouping by status is a kanban board without drag-and-drop. Clicking a title opens a task detail modal (description, status, priority, labels, due date) where Done is the save — the only control that refuses to proceed on an invalid label, while Esc, the backdrop and the X always let you out and discard it; the chevron still gives a cheap in-row peek, and double-click renames inline. Due dates are stored as calendar days, so "due the 14th" reads as the 14th in every timezone; overdue is flagged red, due-today amber. Completed work is archived, not deleted — an item carries an optional archivedAt stamp whose presence is the archived state, so there's one source of truth and no way to store an archived item without a date. Every irreversible action goes through a confirmation dialog that quotes the task by name.

Suggest. A button in the tab header scans the workspace and proposes concrete next tasks — unresolved TODO/FIXME/HACK comments, features the docs promise but the code does not implement, and modules with no tests — each with a rationale, a priority and a file:line pointer where there is one. Tick the ones worth doing and Add selected files them into the backlog; until then they are proposals, stored nowhere. Refresh returns genuinely new ideas because every title already shown joins the exclusion set alongside the existing backlog. The scan runs as a background session that writes a result file the modal polls, on a bounded digest rather than the repository — so it spends tokens, and it is never automatic.

CLI. The package ships a dsh-todo binary so an AI agent can shell out and manage the same list you see in the tab — no profile, no session, no running server:

npx dsh-todo list --open --json
npx dsh-todo add "Fix token refresh" --priority p0 --release 1.5 --due 2026-03-14
npx dsh-todo update t1a2 --status in-progress --sprint 24
npx dsh-todo done t1a2 && npx dsh-todo archive

It targets a workspace directory (--workspace, default cwd) and talks to the same SQLite file the host uses, which is safe by construction: SQLite's file lock refuses a writer that lands inside another process's transaction, and the CLI sets a busy_timeout so it waits for the harness to commit rather than failing. Verified live — the CLI wrote while a running server held its handle and the API returned the new task with no restart. Ids accept any unambiguous prefix, an empty value clears a field (--release ""), and invalid values are refused rather than dropped — nothing is written. Every --json payload leads with ok, so an agent never infers the verdict from the shape: success carries the stored task back for confirmation, and a refusal carries field, expected and got alongside the message. Exit codes are distinct: 0 ok, 2 usage, 3 not found.

How it works. TodoService extends TypertRemoteService registers under the cordis key dshTodo and owns one SQLite database per project at <workspace>/.dsh/todo.db, resolved through workspaceRegistry. Writes use optimistic concurrency: every replace states the revision it observed, and the host rejects a stale write with ok:false, code:'revision-conflict' and the current list, rather than silently clobbering. A legacy central ~/.dsh/storages/dsh_todo.json is migrated on first read.

Endpoints. POST /api/dshTodo/list → { list: { items, revision, updatedAt } }; POST /api/dshTodo/replace → the new list or a revision conflict; POST /api/dshTodo/scanDigest → the bounded workspace digest a scan reasons over; POST /api/dshTodo/readSuggestions → pending until the scan's result file lands, then ready with the parsed suggestions or error, consuming the file either way. All four take a single parameter named request.

The task shape is { id, title, description?, status, priority, release?, sprint?, dueDate?, createdAt, completedAt?, archivedAt? }. status is the source of truth — there is no separate done flag to fall out of sync — and only the status transition writes completedAt. Absent optional fields are absent keys, never ''. Existing v1 databases are migrated in place on first open: CREATE TABLE IF NOT EXISTS doesn't add columns to a table that already exists, so each new column is added with ALTER TABLE after consulting PRAGMA table_info, then title is backfilled from the old text and done = 1 becomes status = 'done'. The v1 columns are still written on every insert, so a downgrade still reads a sane list. src/db.ts is shared by the host and the CLI on purpose — a second copy of the migration is the one duplication that could genuinely corrupt a database.

Requires. workspaceRegistry from dsh-base, which @deepseek-ai/dsh-web-app composes by default. The CLI requires none of it.


dsh-git

npm: @dennisrongo/dsh-git

Source control for the workspace, without leaving the harness. Read what changed, stage it, let the model write the commit message, and push — in the tab you are already looking at, against the same repository your agent is editing.

What you get. A Changes tab showing branch, upstream ahead/behind, and a file list split into staged and unstaged, with per-row stage/unstage/discard and a diff pane. An ✦AI message button drafts a commit message from the staged diff through the harness's own llm service, then Commit all commits. Recent history is listed underneath. A directory that isn't a repository reports repo: false and offers Initialize repository rather than erroring.

Branches, merge, stash and worktrees. The branch name in the header is a menu: switch branches, create one, merge another into it, or delete one. A third Repo tab lists your stashes (push / pop / apply / drop) and your worktrees (add / remove / prune). Every worktree row has an Open button that registers the directory as a workspace and switches to it — a worktree you can see but cannot get into is just a list.

Worktrees go beside the project, named <project>-<branch>. Type a branch and the path fills itself in: feature/login in myproj suggests ../myproj-feature-login, which puts the worktree next to the project on disk and next to it in dsh's workspace list. Slashes are flattened, because ../myproj-feature/login would quietly create a myproj-feature directory with the worktree buried inside it. The suggestion is editable and stops auto-filling as soon as you type your own path.

The model names the branch, not the path. Type a rough description in the branch box — "fix login retry" — hit ✦, and it becomes fix/login-retry, with the path following as ../myproj-fix-login-retry. Naming is a judgement call worth a model; deriving a path from a branch is arithmetic with one right answer, so a regex does that. It fails soft in exactly the way you'd want: no provider configured, or any error, leaves your text untouched with the reason in the log strip — typing the name yourself is always the shortest path.

A worktree can fork from a branch other than the one you're on. The form carries a from select over the local br