vecnode/dsh-vn-plugins--packages-dsh-gittree ↗★ 0
dsh-gittree
History (package and row `dsh-gittree`, kind/address `gittree` / `sidebar://gittree`): a READ-ONLY git history tab type for the dsh-vn-plugins right bar (dsh-rightbar). Lists the session workspace's commits (short id, subject, author, date) in the bar's own dress, with the branch and the current commit kept in the file bar above it; picking a commit shows its message and the files it touched, and any of those files opens through the ordinary file address (so the editor or a shipped preview claims it). A page type like the editor: one guide entry on the tab strip's + / Start page, no core patches, nothing forked, and no git command that can write. Alpha.
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:vecnode/dsh-vn-plugins#e4849fa724696ea1b9cca8fe58995151fb2d0ee1&path:packages/dsh-gittree说明文档
阅读完整 README ↗dsh-gittree (alpha.3)
History is a tab type for the pack's right bar, and it is labelled History in the capsule and the chip while the package, the row and the address keep the dsh-gittree / gittree name (dsh-rightbar — the
right-hand column of the DeepSeek Harness web GUI, beside the shipped Start
page and the Files tab, and the pack's Editor). It shows the commit
history of the tab's own conversation folder, with the branch and the current
commit kept in the file bar above it. Picking a commit opens its message and the
files it touched; any of those files opens through the ordinary file address, so
the editor (or a shipped preview) claims it. Alpha.
It is read-only. Nothing in this package can stage, commit, check out, fetch
or write a config value: the only git subcommands it can reach are rev-parse,
status, ls-files, log, show and diff-tree.
How it plugs in
It is a page type, exactly like the Editor's page tab: it declares no
patterns, so it never competes for a file address.
| Piece | Value |
|---|---|
id / slot key | dsh-gittree |
kind / address | gittree / sidebar://gittree |
priority | builtin |
| guide entry | History, order: 30 — after Files (10) and Editor (20) |
| seats | the keyed sidebar.right.pane.tab / sidebar.right.pane.tab.title |
| services | slots + the bar's sidebarRightTabs (nothing else) |
The tab strip's "+" opens the Start page, which lists the guide entries; picking History creates (or reveals) the page tab.
A file row inside a commit calls the tab record's own openResource action
with a dsh-resource://file/session// address and no
options — the identical call the Files tab makes — so the registry's ranking
decides what claims the file: the pack's editor for text, a shipped preview for
an image or a PDF. The History tab stays open beside it, and this package needs
neither the editor nor any preview to exist.
What it shows
- The file bar: the branch (or
(detached)), the short HEAD commit — the current commit —↑ahead/↓behindwhen there is an upstream, how many files git reports as changed, and the version marker (dsh-gittree 0.1.0-alpha.3) that makes a freshly loaded bundle easy to verify. - The commit list:
short sha, subject, author and date, newest first, up to 80 commits per load. The toolbar's Reload refetches the bar and the list. - A picked commit opens in place (no navigation, no new tab): its full id,
author, date, message body, and the files it touched with
M/A/D/R/C/Tbadges. Every one of those rows opens that file in whatever claims it. - An empty repository (no commits yet) and a workspace with no commit of its own are told apart from a failure: the panel says which.
There is no working-tree file listing and no viewer switcher — the Files tab already browses the folder, and the editor opens what a commit row points at.
The read-only routes (Node half)
The browser cannot read a repository, so the row owns three authenticated
connection.fetch routes under /api/dsh-gittree/* — the same mechanism
dsh-editor uses:
| Route | Git behind it | Answers |
|---|---|---|
GET /api/dsh-gittree/state?session= | rev-parse --show-toplevel, status --porcelain=v2 -z --untracked-files=all --branch, ls-files -z, rev-parse --short HEAD | { root, repoRoot, scope, branch, head, detached, upstream, ahead, behind, entries, total, changed, truncated } |
GET /api/dsh-gittree/state?session=&brief=1 | the same branch/status/HEAD calls, without ls-files or the entry merge | { brief: true, root, repoRoot, scope, branch, head, detached, upstream, ahead, behind, changed } — what the tab's bar shows, and nothing else |
GET /api/dsh-gittree/history?session=&limit=N | log -n N --date=short --pretty=format:… (scoped to the workspace when it is a subfolder) | { commits: [{ sha, short, author, date, subject }] } |
GET /api/dsh-gittree/commit?session=&sha= | show -s --pretty=format:… + diff-tree --root --no-commit-id --name-status -r -z | { commit: { sha, short, author, email, date, subject, body }, files: [{ status, path, origPath? }] } |
The tab itself uses brief=1 (the bar never needs a file list), so a GUI that
is showing history never pays for ls-files or the entry merge; the full form
remains for anything that wants the tree, and the tracked check covers both.
Design points worth keeping:
- The session id is the only input. The workspace root is resolved host-side —
live session header first, session persistence second, a typed
NO_WORKSPACEotherwise — exactly likedsh-editor. The client never names a path on disk. - Scope. When the conversation folder sits inside a repository (a monorepo
package, a session opened on a subdirectory), the history is scoped to that
folder and every path is reported workspace-relative, so a row click can be
handed straight to the file-address grammar. Both sides of that comparison go
through
realpath— a session header can carry a Windows 8.3 short path (LUISAR~1) or a symlinked path, and a relative path computed across two spellings of the same folder is garbage. - Parsed, not string-matched.
status --porcelain=v2 -zis walked as NUL-separated tokens (a rename's source path is the next token; paths may contain spaces), anddiff-tree -zyieldsSTATUS\0path\0pairs.--rootis what makes a repository's first commit list its files at all. - No shell, no injection, no holes. git is spawned with an argv array and no
shell; every argument is a literal in
lib/index.jsplus, at most, a commit id that must match/^[0-9a-fA-F]{4,40}$/before it can reach argv — so--allor any other option is rejected asBAD_REQUEST. The environment is pinned (GIT_OPTIONAL_LOCKS=0,GIT_TERMINAL_PROMPT=0,LC_ALL=C,--no-pager), the call is killed after 10 s and its output capped at 8 MiB, and a failure is typed (NOT_A_REPO,GIT_MISSING,TIMEOUT,TOO_LARGE,GIT_FAILED) so the surface can say why instead of "failed". - Every request carries a token, never an effect cleanup. An answer is applied
only while it is still the newest one. alpha.1 shipped the opposite (the
effect's cleanup cancelled the request it had just started on the next render),
which left the panel on "Reading the history…" forever — the shape of that bug
is what the comment in
lib/client.jswarns about. - Lazy. Nothing runs until the tab is shown, and the route responses are
no-store.
Layout
cordis.patch.yml bundle layer: inserts the 'gittree' row (nothing else patched)
lib/index.js Node half: the read-only /api/dsh-gittree routes above
lib/client.js Browser half: the page tab type + guide entry, the commit list
and a commit's detail (module-table bundle, no build step)
Nothing is forked and no core row is disabled: this package adds surface, so
scripts/sync-vendored.ps1 has nothing to keep in sync for it.
Verification
scripts/checks/check-node-routes.mjs drives these routes against a real
scratch repository it creates (init → commit → modify → untracked → a workspace
that is a subfolder), asserting the scope, the status codes, the brief form, the
root commit's file list and the option-injection guard;
scripts/checks/check-client-bundles.mjs loads the browser half through the
module table and a real React runtime and checks the registration, the guide
order, the history-only body and the chip title.
Install / uninstall
The repo launcher (install.bat on Windows, ./install.sh on macOS/Linux)
auto-discovers this package — it is a standard dsh.bundle. Adding a package
changes the profile's bundle set, so the first install after it appeared needs
one plain launcher run (or -Force). The web profile links it into this repo, so
code edits only need a restart of npx @deepseek-ai/dsh web plus a hard browser
refresh. git must be on PATH for the routes to answer; without it the tab
says so (GIT_MISSING) instead of failing silently.