dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Git branches and worktrees for the DeepSeek Harness (dsh) Web Client. On the new-session screen it adds a project switcher and a branch pill with a worktree checkbox. Tick it and your first message runs in a fresh git worktree on a new branch named after that message. Once a session is running, a slim row above the composer shows the branch and worktree it is in, and lets you switch, create, rename, delete, lock, and prune them. git runs on the host. The browser never runs a git command itself.
On a blank new chat, the row above the composer reads: project switcher, the harness's own mode chip, and a branch pill holding the branch and a worktree checkbox.
Project switcher. Opens a searchable list of your workspaces with Browse for folder… at the bottom. That entry opens the host's folder chooser, registers the folder as a workspace, and moves the new session there. If you have no workspaces yet, opening the switcher goes straight to the chooser.
Branch pill. Shows the branch the session will start on.
When a repository has more branches than maxBranches, the list says so. Typing a search then also asks the host, so a branch past the limit can still be found.
First send with the worktree ticked. When you press Enter or the send button, the plugin:
branchPrefix is prepended, and a taken name gets a -2, -3, … suffix;git worktree add -b <name> from the chosen base branch, at the path worktreePathTemplate gives;If you leave the session before the worktree is ready, or the move does not finish in time, the workspace, the worktree, and the new branch are removed again. Your message stays unsent in the composer, and you get a notice.
Pill states. Whenever a project is selected, the pill keeps its place in the row:
| Pill reads | Meaning |
|---|---|
| grey placeholder | The first reading has not arrived yet. |
| no git (disabled) | The folder is not a git repository, git cannot run on the host, or showChip is off. The tooltip says which. |
| git error (disabled) | The reading failed, for example on a timeout. The tooltip shows git's message, and the reading is retried with backoff. |
After the first message, a row directly above the composer shows ⑂ branch ↑ahead ↓behind • and the current worktree's name with a count of worktrees. The dot means uncommitted changes. A + button next to it creates a new worktree. The row re-reads the repository every refreshIntervalMs. The popovers open upward.
The row is hidden in sessions whose folder is not a git repository, when git is unavailable, and when showChip is off.
Branch popover. Local and remote-tracking branches, each with its tip commit, author, age, and ahead/behind counts, plus a search field.
git switch --merge, which aborts on conflict. Without it, git refuses a switch that would overwrite changes.git fetch --all --prune.Worktree popover. Every worktree of the repository, marked main, current, detached, locked, or prunable.
git worktree prune.New worktree dialog (the + button). Choose a new or existing branch and an optional start point. The directory is prefilled from worktreePathTemplate and can be edited. It must not exist, or must be an empty directory. Register as a workspace and Open a session after creating default to registerWorkspace and openSession.
Destructive actions.
confirmDestructive is on.git worktree remove deletes ignored files (a .env, a build directory) even without force. So the remove dialog lists them first and needs its own checkbox before removing them, whatever confirmDestructive is set to. The host refuses such a removal without that acknowledgement.Settings → Plugins → Worktrees and branches shows whether git was found on the host, with its version, and edits the preferences. See Configuration.
web profile. This is the version the plugin is tested against. Node ^22.19 || >=24, as in engines.PATH, because the plugin uses git switch. With git 2.36 or newer, the plugin reads the worktree list in NUL-separated form and also reads the locked and prunable flags. With older git it reads the plain listing, which does not show those two flags.PATH, because dsh plugin runs pnpm.@deepseek-ai/dsh-subprocess and @deepseek-ai/dsh-fs, required to run git. Without either, the row stays hidden, the pill reads no git, and the settings card names what is missing.dsh-client-ui-directory-picker-native or -browse) for Browse for folder…. Without one, that entry reports that no chooser exists.llm and agentDefaultModel for model-written branch names. Without them the name is a slug of the message.cordis.patch.yml apply and the card cannot save changes.dsh plugin --profile web add @achasoft/dsh-worktree
dsh web
dsh plugin --profile <name> … runs pnpm with the remaining arguments in $DSH_HOME/profiles/<name> (default ~/.dsh/profiles/web), creating the profile first if it does not exist. After pnpm succeeds, dsh adds every dependency whose package.json declares dsh.bundle to that profile's dsh.profile.bundles list. This package declares "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }, so it becomes a profile layer with no manual edit. Restart dsh web after installing.
To uninstall, remove the package. dsh also drops it from dsh.profile.bundles:
dsh plugin --profile web remove @achasoft/dsh-worktree
cordis.patch.yml is appliedAt boot, dsh builds the configuration from patch layers, in this order:
cordis.patch.yml, in dsh.profile.bundles order. This package's file inserts two rows.$DSH_HOME/profiles/<name>/cordis.patch.yml.$DSH_HOME/cordis.patch.yml, which applies to every profile.--patch <file> overlays.A later layer overrides an earlier one by row id. The two rows this package inserts:
| id | name | Role |
|---|---|---|
worktree | @achasoft/dsh-worktree/host | Host service: git over RPC, and the worktree settings section. |
worktree-ui | @achasoft/dsh-worktree | Browser half. It must be the bare package name, because the Web Client finds browser code by resolving <row name>/package.json. |
To see the composed result:
dsh --profile web --dump-config
Every key lives in the config of the worktree row. To override keys, add the row to your profile's cordis.patch.yml by id. Restate every key, because a patch replaces the row's whole config:
- id: worktree
config:
showChip: true
worktreePathTemplate: '{repoParent}/{repo}-worktrees/{branch}'
branchPrefix: 'feature/'
registerWorkspace: true
openSession: true
includeRemoteBranches: true
maxBranches: 200
refreshIntervalMs: 15000
gitTimeoutMs: 20000
networkTimeoutMs: 120000
maxOutputBytes: 1048576
graceMs: 5000
confirmDestructive: true
The host schema declares no defaults, and every key except branchPrefix is required. The defaults below are the values cordis.patch.yml ships. Values saved from the settings card are stored as a user layer over the patch value. The host reads settings on each request, so changes need no restart.
| Key | Default | Card | What it does |
|---|---|---|---|
showChip | true | yes | Shows the row above the composer and an enabled new-session pill. When off, the pill reads no git and the RPC endpoints and settings card stay available. |
worktreePathTemplate | {repoParent}/{repo}-worktrees/{branch} | yes | Directory for a new worktree. See Path template. |
branchPrefix | '' | yes | Prefix added to new branch names: first-send names, and branches created from the branch popover or the create dialog. Example: feature/. |
registerWorkspace | true | yes | Default for Register as a workspace in the create dialog. |
openSession | true | yes | Default for Open a session after creating. Requires registerWorkspace. |
includeRemoteBranches | true | yes | Lists remote-tracking branches next to local ones. |
maxBranches | 200 | yes | Most branches returned per reading (minimum 1). The list says when it was cut. |
refreshIntervalMs | 15000 | yes, in seconds | How often the row re-reads. 0 means read only on explicit refresh. |
gitTimeoutMs | 20000 | yes, in seconds | Time limit for a local git command (minimum 1000). |
networkTimeoutMs | 120000 | yes, in seconds | Time limit for fetch (minimum 1000). Must not be less than gitTimeoutMs. |
maxOutputBytes | 1048576 | no | Memory cap for each captured git output stream (minimum 4096). |
graceMs | 5000 | no | Delay between TERM and KILL when a git command is stopped. |
confirmDestructive | true | yes | Requires typing the name before a force-delete or force-remove. |
The host refuses a configuration at load, and the card refuses to save it, when:
worktreePathTemplate is empty or contains neither {branch} nor {branchPath};networkTimeoutMs is less than gitTimeoutMs;openSession is true while registerWorkspace is false.The template expands against the repository being acted on. A relative result resolves against {repoRoot}.
| Placeholder | Expands to |
|---|---|
{repoRoot} | The repository's main worktree directory. |
{repoParent} | Its parent directory. |
{repo} | Its directory name. |
{branch} | The branch name with / replaced by -, so feature/login becomes one directory. |
{branchPath} | The branch name with slashes kept, so feature/login nests. |
The plugin registers no model tools, prompt content, or session events. The browser half calls one Typert Remote namespace, worktree, over the harness's own client connection:
| Endpoint | Purpose |
|---|---|
describe | Whether git is available, its version, and the preferences the browser draws from. |
overview | HEAD, dirty counts, branches, and worktrees for one workspace directory. |
searchBranches | Case-insensitive branch-name search past maxBranches. |
fetch | git fetch --all --prune --quiet. |
checkout, createBranch, renameBranch, deleteBranch | Branch changes. |
suggestBranchName | Branch name from a prompt, written by the model or as a slug. |
suggestPath, addWorktree, inspectWorktree, removeWorktree, lockWorktree, pruneWorktrees | Worktree changes. |
Every endpoint returns a result value with a failure code (no-git, not-a-repository, refused, timeout, …) instead of throwing. The root export @achasoft/dsh-worktree re-exports these types for other packages.
ctx.subprocess, with GIT_TERMINAL_PROMPT=0, GIT_PAGER=cat, and LC_ALL=C. The plugin registers no HTTP routes. All calls go through the harness's own client connection.-c core.fsmonitor=false.filter.<driver>.clean/process defined in the repository's local or per-worktree config, including include.path files. They also pass --no-optional-locks and --ignore-submodules=dirty.= cannot have that filter disabled on the command line, so it is not read automatically.- is refused.rev-parse --verify.worktree list.-- ends the options wherever git's parser accepts it.--force. Force-delete and force-remove are separate, confirmed actions. Ignored files need an explicit acknowledgement that the host enforces.expectedTip./. A hidden element in the composer's tool row (conversation.input.left) listens, in the capture phase, for Enter and for clicks on the send button. It acts only while a worktree is ticked, the draft does not start with /, no trigger menu owns Enter, no IME composition is in progress, and the send button is enabled. A future change to the composer's DOM, such as the send button no longer being the card's last button, would need this updated.conversation.hero.workspace at priority -1. The lowest priority renders, and the core picker is at 0. The core menu's Add workspace… entry is replaced by Browse for folder…. Uninstalling returns the core picker.@deepseek-ai/dsh-client-ui-conversation 0.1.5-rc.2) to decide when to show.The dev dependencies are link: specifiers to a DeepSeek Harness source checkout at ../../deepseek-harness, relative to this directory. Clone the harness there before installing.
pnpm install
pnpm run typecheck
pnpm test # checks generated/ against src/host, then runs vitest
pnpm run build # tsc -p tsconfig.build.json, then tsdown -> lib/
generated/ holds the Typert RPC contract and is committed source. The harness's generator only runs inside a harness checkout, so scripts/emit-typert.mjs writes the contract in that generator's format. If you change src/host/types.ts or the @Remote methods in src/host/index.ts, update the spec in that script and regenerate:
pnpm run regen:typert # rewrites generated/ and generated/.fingerprint
pnpm run check:typert # the same check pnpm test runs first
To load your checkout into a local profile, build it, then add it by path. dsh resolves a relative path against the directory you run it from:
pnpm run build
dsh plugin --profile web add "$(pwd)"
dsh web
The profile loads the built lib/ output, not src/, so build before adding the checkout. After changing browser code, rebuild and reload the page. After changing anything under src/host/, or regenerating generated/, restart dsh web.
MIT