Development & Runtime
dsh-ui-gitworkbench
young1lin/dsh-ui-gitworkbench · ★ 1
An out-of-tree Web UI plugin for dsh (DeepSeek Harness): installs a Git workbench into dsh's web interface without touching dsh itself.
Manifest details
安装
dsh plugin --profile web add github:young1lin/dsh-ui-gitworkbenchGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
An out-of-tree Web UI plugin for dsh (DeepSeek Harness) that adds a Git workbench to the dsh web interface — without touching dsh itself.
Every session header gets a small status card showing the current branch, ahead/behind counts, and added/deleted lines. Click it and a workbench panel slides in from the right with everything about the session worktree's changes:
- Changes — a collapsible file tree beside full-context, side-by-side per-file diffs with dual line numbers, word-level highlights, Shiki syntax coloring, and an editable right column. A binary file is sniffed by its bytes and drawn when it is a picture (a deleted one shows HEAD's copy). A magnifier filters the tree by space-separated keywords (smart case); hovering a file reveals IDEA-style Rollback behind a consequence-specific dialog. The diff header keeps the current hunk and
current / totalvisible: Unstaged offers Stage / Revert even in Edit mode, while Staged offers Unstage for the current hunk or the whole file. - Files — a searchable repository tree with editable CodeMirror file views, image previews, and per-line blame information. Ctrl/Cmd+F opens a find panel dressed in the drawer's own controls that reports
3/128— which match you are on, out of how many. - History — commits, file tree, and diff in three panes; scrolling to the bottom loads the next page automatically. Ctrl/Cmd+F finds in the diff (Enter / Shift+Enter walk the hits,
current / totalcount, hits painted); a picture shows the commit's copy (a deleted one, the parent's). Rows carry the author, the hover card the exact time; an IDEA-style filter (auser:/path:/after:query grammar plus an authors / date / paths funnel) compiles intogit logarguments and matches over all history, with an All-branches option. - Compare — diff any two branches against each other, with the same find bar; a picture shows the head ref's copy (a deleted one, the base's).
- Commit & sync — ticking a file in the tree is a real
git add/git restore --staged; with the commit box and the fetch / pull / push bar, a full commit-and-push never leaves the panel. The header also carries a branch switcher (main worktree only): the current branch is marked, a branch held by another worktree is disabled with its whereabouts on hover, a remote-only branch checks out and tracks in one pick — local edits ride along when git can carry them and stop it (classified, intact) when it cannot; never forced. - Appearance — seven theme families in light and dark (following the OS by default), plus a blurred background image and custom CSS, stored per project and globally with the project scope winning.
It also ships worktree emulation: the model can call the worktree_enter / worktree_exit / worktree_status agent tools to create or leave an isolated worktree under .agents/worktrees/<name> and bind the session to it. The name doubles as the branch by default; pass branch to choose another — unlike the name it may contain slashes (feature/foo), the spelling a Windows directory name can never hold. A subagent session never writes a binding of its own — it borrows the nearest bound ancestor's worktree along its lineage: the standing prompt, the chip, and worktree_status all resolve that effective binding for a session without one, and the borrow disappears when the parent exits. The status card lights up its binding marker, the panel header gains a worktree switcher listing every worktree in the repository, and the stats follow the binding.
The Chinese README.md is the project's deep handoff document — including the pitfall catalog (§6) that every contributor should read before changing the build, the RPC layer, or the Windows-specific handling.
Install
Prerequisites: dsh installed and dsh web running; Node.js ≥ 20, pnpm ≥ 10.
Recommended — the official plugin channel, one command:
dsh plugin --profile web add @young1lin/dsh-ui-gitworkbench
Then restart dsh and hard-refresh the browser (Ctrl/Cmd + Shift + R). The package declares its dsh.bundle.patch, so the CLI registers the host half into the profile's dsh.profile.bundles and mounts it on the next start — you never hand-write a cordis.patch.yml entry. Without a dsh command on PATH, run it through npx:
npx -y --package @deepseek-ai/dsh dsh plugin --profile web add @young1lin/dsh-ui-gitworkbench
Upgrading an installed copy uses update, not a repeated add:
dsh plugin --profile web update @young1lin/dsh-ui-gitworkbench
dsh plugin is a thin pnpm forwarder: repeating add does not error, but it reinstalls the latest version and overwrites a link: install (a from-source checkout suddenly reverts to the npm build). update reconciles by installed state, so a dsh.bundle declaration a newer version gained is activated automatically. Restart dsh afterwards either way.
# macOS / Linux (or Windows with Git Bash / WSL)
curl -fsSL https://raw.githubusercontent.com/young1lin/dsh-ui-gitworkbench/main/scripts/install.sh | bash
# Windows (PowerShell 5.1+ / pwsh)
irm https://raw.githubusercontent.com/young1lin/dsh-ui-gitworkbench/main/scripts/install.ps1 | iex
Beyond the install command, the scripts pre-write pnpm 11's minimumReleaseAgeExclude so a release younger than 24 hours installs immediately, and idempotently remove legacy manual mount lines (a double mount shows two status cards on the page). They also take options — pinning a version, pm2 restart dsh-web afterwards, --dry-run — documented at the top of each script.
dsh plugin --profile web add <repo path> installs the checkout into the profile. After editing the client half, run npx tsdown and refresh the browser; host-half changes need a dsh restart. When switching a link: dependency back to the npm version, remove the manual mount line from cordis.patch.yml (the installer scripts do this automatically).
For maintainers
pnpm install # .npmrc keeps auto-install-peers off; the dsh peers come from the web profile at runtime
pnpm typecheck # both tsconfigs — the client half is otherwise never checked
pnpm test # vitest, full suite
pnpm bundle # tsc (host half: lib/index.js) + tsdown (client half: lib/client.js)
The plugin has two halves with different feedback loops: the host half (src/index.ts and friends) is a TypertRemoteService exposing the RPCs behind every view, and must be rebuilt and followed by a dsh restart; the client half (src/client/**) is a closure-factory bundle that the web server re-reads from disk on every request, so a rebuild plus a browser refresh is enough. Pure logic lives in React-free modules so vitest can load it directly.
Releases are tag-driven: pushing a vX.Y.Z tag runs the CI checks and publishes to npm through OIDC trusted publishing — no npm token anywhere. The full runbook (first manual publish, trusted-publisher setup, why the tarball carries no sourcemap) lives in the Chinese README's maintainer section.
Known limits
- The status card mounts in the
conversation.session.header.actionsslot, so it appears only once a session is actually open. In headless automation without a real session the card will not show — open a session in the UI to see it. - Person search in the history filter matches the author only (IDEA parity — git has no author-or-committer pushdown). The committer stays visible in the hover card.