dsh-plugin-herdr
Herdr control-plane plugin for DeepSeek Harness (DSH): observe and drive Herdr — a terminal workspace manager for AI coding agents — from DSH sessions
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 18, 2026
Introduction
dsh-plugin-herdr
English | 简体中文
Herdr control-plane plugin for DeepSeek Harness (DSH): observe and drive Herdr — a terminal workspace manager for AI coding agents — from DSH sessions.
- 19
herdr_*tools: snapshot, agent list, agent start/wait/prompt/ explain/send-keys, pane run/read/split/send-keys, workspace create/close/ rename, pane close/rename, pane layout, layout apply, notification — all over the Herdr socket protocol (JSONL over a Unix-domain socket; the CLI transport has been removed). - Herdr panel & tab, scoped to the session: both the conversation-page Herdr tab and the right-side floating pane list show only the current session's dedicated workspace and its panes. Mode-gated UI: conversations that are not in "Herdr 模式" show no Herdr tab, panel, or header pill at all.
- herdr mode (agent preset): create a session in "Herdr 模式" — the session gets a dedicated workspace created in the project directory, every pane the session produces (splits, agents) lives in that workspace, and the whole workspace is reclaimed when the session ends.
- Open real agents:
herdr_agent_startstarts a coding agent (pi / codex / claude) in the session workspace and waits until Herdr recognizes it; submit work withherdr_agent_promptand wait withherdr_agent_wait. - Global Herdr Dashboard: a machine-level read-only overview of the local
Herdr server (all workspaces across sessions/projects) — opened from the
left sidebar button right under New Session. The button is a DOM marker
injected into the sidebar's document flow between New Session and the
workspace/session browser (plugin-only, via the existing
shell.overlayslot host — no DSH changes required; a MutationObserver re-inserts it after React re-renders/collapse), with a status dot (running / stopped / not-installed / checking; reused from the shared/herdr-statuspoll). Clicking opens a full-height page covering the right work area (from the sidebar's right edge; the sidebar stays visible). The page shows server/socket/host cards (process metrics merged into the Herdr server card), a full agent-name list, and per-workspace agent-kind treemaps; closing the page returns to the current conversation (header ✕ / Escape). Treemap blocks are clickable: a block whose kind has exactly one agent jumps to that pane in the current session's Herdr tab (the pane must belong to the current DSH session — resolved via a reverse lookup/herdr-pane-session); panes of other sessions (or unbound panes) keep the panel open and show an inline notice instead. Workspace cards themselves are not clickable (blank areas and the header row do nothing) — everything stays strictly read-only with no cross-session navigation.
Install
Prerequisites: a DSH profile (e.g. web) and a running herdr headless
server (the panel's start button spawns herdr server from PATH — the only
remaining CLI invocation, see "Platform support").
dsh plugin is a thin pnpm forwarder: it runs pnpm <args> inside the
profile directory ($DSH_HOME/profiles/<name>/), then reconciles the
dsh.profile.bundles layer list against the installed state — this package
declares dsh.bundle.patch, so installing it joins the profile's bundle
layers (which is how its cordis.patch.yml gets applied).
# local directory
dsh plugin --profile web add /path/to/dsh-plugin-herdr
# tarball (pnpm pack output)
pnpm pack
dsh plugin --profile web add ./dsh-plugin-herdr-*.tgz
# git
dsh plugin --profile web add github:sunny0826/dsh-plugin-herdr
pnpm allowBuilds: git-hosted installs run the package
preparescript. If pnpm blocks it, add the exact key pnpm printed underallowBuildsin<profile>/pnpm-workspace.yaml, then re-run.
Verify the install with dsh plugin --profile web list (or
dsh plugin --profile web why dsh-plugin-herdr).
After install, restart the profile. The plugin registers ctx.herdr, the
tools, and the Herdr panel; the "Herdr 模式" preset appears in the new-session
picker (copied to $DSH_HOME/.agent-presets/herdr/ on first load).
Uninstall
Remove the plugin from the profile — dsh plugin ... remove forwards to
pnpm remove and drops the package from the dsh.profile.bundles layer list
in the same step:
dsh plugin --profile web remove dsh-plugin-herdr
Then restart the profile: the herdr_* tools, ctx.herdr, the Herdr panel,
and the herdr-mode wiring are gone.
The uninstall leaves three traces worth knowing about:
- Agent preset — the "Herdr 模式" preset was copied to
$DSH_HOME/.agent-presets/herdr/and is not removed. It stays in the new-session picker but is inert without the plugin; delete it manually:rm -rf "$DSH_HOME/.agent-presets/herdr". - Herdr workspaces — herdr-mode sessions own a dedicated
dsh:<project>workspace that is reclaimed when the session ends. Close open herdr-mode sessions before uninstalling; any leftovers can be closed from the herdr CLI (herdr workspace list/herdr workspace close <id>). - Profile config — herdr config entries you added to the profile's
cordis.patch.yml(e.g.timeoutMs) become inert; remove them for a clean profile.
Reinstalling is just the Install section again: the bundle layer is re-added and the preset is re-copied on first load.
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
socketPath | string | – | herdr socket path (HERDR_SOCKET_PATH); POSIX only |
session | string | – | herdr session name (HERDR_SESSION) |
timeoutMs | number | 30000 | per-command timeout |
allowBackground | boolean | false | expose run_in_background on pane run |
events.enabled | boolean | false | subscribe to Herdr events |
events.maxReconnectMs | number | 30000 | event subscription reconnect cap |
reportState | boolean | true | report DSH→Herdr state inside a pane (HERDR_ENV) |
projectRoot | string | – | project directory for server-side filtering; defaults to process.cwd() |
Preset configuration (presets/herdr/agent.cordis.yml, herdr-session-mode):
| Key | Default | Description |
|---|---|---|
paneId | '' | Fixed pane binding shared by all sessions; empty = every session creates its own dedicated workspace |
label | '' | Display label override; empty = auto dsh:<project name> (cwd basename, falls back to dsh:<short session id>) |
cwd | – | Workspace working directory; empty = the session's project directory |
Output limits (CA-014):
- Per-command output is capped at 1 MiB per stream;
pane_read/pane_runreporttruncated: truewhen the cap is hit (the server-reportedtruncatedflag is surfaced as-is).
Platform support: the plugin is POSIX-only — all control-plane interaction
goes over the herdr Unix-domain socket (JSONL), and the panel's start button
spawns herdr server from PATH as the single bootstrap exception. Windows
(named pipe) is not supported: the plugin refuses to load without a
resolvable socket path. Use POSIX (macOS/Linux).
Example patch (cordis.patch.yml):
- id: dsh-plugin-herdr-client
name: dsh-plugin-herdr/client-entry
config:
timeoutMs: 15000
- id: dsh-plugin-herdr
name: dsh-plugin-herdr
config:
timeoutMs: 15000
Tools
| Tool | Description |
|---|---|
herdr_snapshot | Session snapshot: workspaces, tabs, panes, agents, focus |
herdr_agent_list | List agents (filter by workspace / status) |
herdr_agent_start | Start a coding agent (pi / codex / claude) in a pane and wait until Herdr recognizes it; default pane = a new split inside the session workspace |
herdr_agent_prompt | Submit a prompt to an agent, optionally wait for a state |
herdr_agent_wait | Wait for an agent to reach a state |
herdr_agent_explain | Explain agent detection state |
herdr_agent_send_keys | Send keys to an agent |
herdr_pane_run | Run a shell command in a pane; reuses the session's bound pane by default (a new split only when there is no bound pane) |
herdr_pane_read | Read pane terminal output (visible/recent) |
herdr_pane_split | Split a pane (direction/ratio/cwd/env) |
herdr_pane_send_keys | Send key presses to a pane |
herdr_pane_layout | Read a pane's layout |
herdr_pane_close | Close a pane — destructive |
herdr_pane_rename | Rename a pane (pane_id, label may be empty/null to clear the name) |
herdr_workspace_create | Create a workspace (refused in herdr 模式 — the session already owns one) |
herdr_workspace_close | Close a workspace and all its panes — destructive |
herdr_workspace_rename | Rename a workspace (workspace_id, non-empty label ≤64 chars) |
herdr_layout_apply | Apply a declarative layout |
herdr_notification | Show a system notification |
herdr mode (agent preset)
Select Herdr 模式 when creating a session. The session:
- gets a dedicated workspace created in the project directory (session cwd) at session start; its root pane is the session's bound pane;
- keeps every pane it produces (pane_run splits, agent_start, pane_split)
inside that workspace —
herdr_workspace_createis refused so nothing leaks out; - reports
working/idleto the Herdr sidebar (pane.report-agent); - closes the whole dedicated workspace when the session is disposed (fixed bindings are only released);
- survives process restarts: the bound pane carries an internal marker
(
tokens.dsh_session = <sessionId>, permanent), so a restarted instance reuses the same pane instead of creating duplicates.
Naming conventions
- Display name (workspace / bound-pane label):
dsh:<project name>(session cwd basename;dsh:<short session id>fallback). Configlabeloverrides. - Internal marker is kept separate from the display name: the pane's
tokens.dsh_session(not the label), so no session id leaks into visible names. - Agent names:
herdr_agent_startauto-generates<kind>-<n>(e.g.pi-1); passnameexplicitly for<kind>-<purpose>(e.g.pi-disk-check).
Opening an agent to do work
herdr_agent_start {kind: 'pi'} # starts in a new pane inside the session workspace
herdr_agent_prompt {target: '<pane_id>', text: '...'} # submit the task
herdr_agent_wait {target: '<pane_id>', until: [idle, done, blocked], timeout_ms}
herdr_pane_read {pane_id: '<pane_id>'} # read the result
One-shot commands like pi --print "..." are not Herdr agents —
herdr_agent_wait cannot track them; use herdr_agent_start instead.
Herdr panel interactions
The conversation-page Herdr tab and the right-side floating pane list are scoped to the current session (mode-gated: hidden entirely outside herdr 模式):
- Panes view: the Herdr tab shows only the session's dedicated workspace and its panes (the global dashboard lives in the left sidebar — see above; the old in-tab Dashboard sub-view/button has been removed).
- Session workspace only: both views show just the session's dedicated workspace and its panes (no project/all scope toggle — that concept is gone).
- Two-column cards + drag sort: panes render as a two-column grid; dragging
the ⋮⋮ handle reorders within a workspace. Order persists in localStorage key
herdr:pane-order:<workspace_id>. Cross-workspace drags are ignored. Reacts to narrow viewports (<640px → single column). - Log preview/expand: card body shows the latest lines with a fade-out; "展开" gives an independently scrolling log that auto-follows a working agent and offers "复制" (full output).
- Rename: ✎ or double-click turns the pane/workspace name into an inline input (≤64 chars); clearing the pane name removes it (falls back to title). Renames are persisted by herdr server.
- Close: ✕ (hover) opens a confirm dialog; a workspace close shows its pane count. The dialog and the server both refuse closing the pane that hosts the current session (self-pane).
- Herdr tab logo: the tab is labelled with the herdr logo (CSS-masked, theme-aware) instead of text.
Safety boundary
- All actions are local to your machine; the plugin talks to the local herdr socket (the only subprocess spawn is the optional server-start bootstrap).
- The panel endpoints (
/herdr-status,/herdr-dashboard,/herdr-start,/herdr-session-pane,/herdr-pane-session,/herdr-close,/herdr-rename) are plain HTTP on the local web server — do not expose the DSH web port publicly. They are additionally guarded (CA-007):- strict methods:
/herdr-status,/herdr-dashboard,/herdr-session-pane&/herdr-pane-sessionare GET-only (read-only),/herdr-start,/herdr-close&/herdr-renameare POST-only (otherwise405 + Allow); - local-context only:
Hostmust belocalhost/127.0.0.1/::1(DNS-rebinding defense); cross-siteOriginorSec-Fetch-Site: cross-siteis rejected with403(CSRF defense) — unauthorized requests cannot start the herdr server or read terminal/topology data.
- strict methods:
- State reporting is display-only: it does not affect Herdr's own wait or notification semantics.
Troubleshooting
| Symptom | Fix |
|---|---|
HERDR_UNAVAILABLE: "herdr socket not found" | Start the Herdr headless server (herdr server or the panel's start button); install herdr first: `curl -fsSL https://herdr.dev/install.sh |
| Plugin fails to load with "requires a resolvable socket path" | Windows is not supported; on POSIX set socketPath/HERDR_SOCKET_PATH |
| No "Herdr 模式" preset | Check $DSH_HOME/.agent-presets/herdr/ exists (plugin recreates it on load) |
| Panel stuck on "正在获取本会话 pane…" | The session was switched into herdr 模式 after creation or the server restarted; the first model request triggers a fallback bind — send a message, or restart the profile |
herdr_agent_start fails with agent_pane_busy | Transient: a freshly split pane's shell is still initializing; the tool retries automatically — check again shortly |
Development
pnpm install
pnpm build # tsdown (node entries + web client bundle)
pnpm quality # typecheck + gen-types drift check + unit tests
pnpm test # unit tests (node --test)
pnpm test:integration # build + run.mjs + extended.mjs + events.mjs + close-rename.mjs (real herdr; SKIPs when unavailable)
pnpm gen:types # regenerate protocol types from the herdr schema fixture