DeepSeek Harness Plugin

aiyacharley/dsh-at-sider

Stars ★ 0 Downloads (30d) 1,287 Category UI Enhancements Added 2026-10-22 npm dsh-at-sider

Enhances the native right-sidebar Files tab rather than replacing it: each row gets an @ reference button that inserts the canonical path mention into the composer, plus a size column, a sortable modification-time column, and a status dot for untracked, unstaged and staged files. Below the tree a git bar shows the branch with ahead/behind counts and expands upward into the 20 most recent commits, with a repository selector when a workspace has independent repositories in subdirectories. A header search box walks the workspace recursively and can open a result or expand the tree at it, preview tabs offer reveal-in-tree and one-click @, the tree is keyboard navigable with ARIA tree semantics, and a right-click entry on the Files tab switches back to the native tree at runtime.

Install

# from npm (prebuilt)

dsh plugin --profile web add dsh-at-sider

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:aiyacharley/dsh-at-sider

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

简体中文 | English

Six things the native sidebar file tree was missing: @ references · file size · modification time · sorting · quick search & locate · git commit info at the bottom of the sidebar. Same tab, same entry point, same icons, same way of opening files — the right sidebar's Files tab stays the native one (same Mod+P, same guide capsule, same artwork):

  • Each row: a @file chip right after the name + size / modification time pinned to the far right (width-adaptive, never clipped);
  • Header: whole-workspace quick search (recursive, results can be @-ed or jumped to) + sorting (name / time / size / type, remembered);
  • Bottom git bar: branch, ahead/behind, and the latest commit summary — click to expand the last 20 commits upward; multi-repo workspaces get a repository switcher, and files are coloured by state (untracked / unstaged / staged);
  • Right-click a file preview: one-click @file and reveal in tree (ancestors expanded, the row highlighted);
  • Right-click the Files tab to switch back to the native tree whenever you want to compare (no uninstall, switch back any time) — all of it keyboard operable.

Contents


🚀 Install (two minutes)

Prerequisites: Node.js ≥ 20, then install the DSH CLI globally (recommended — you get the dsh command):

npm install -g @deepseek-ai/dsh
dsh web        # boot the DSH web environment (or: npx @deepseek-ai/dsh web)

With the app running, one command installs this plugin:

# one command (official CLI, recommended)
dsh plugin --profile web add dsh-at-sider@latest
# or from GitHub: dsh plugin --profile web add github:aiyacharley/dsh-at-sider
# or from a local checkout: dsh plugin --profile web add /path/to/dsh-at-sider

Then restart DSH (dsh web) and open the right sidebar's Files tab (or press Mod+P). Three checks:

  1. hover any row — an @file chip appears right after the name (@folder on directories);
  2. the row's far right shows its modification time, e.g. 2026-01-02 11:04, with the full local time on hover;
  3. click @file — the file's @mention lands in the composer (hold Alt/⌥ to copy the mention instead).

No configuration. Every install route (agent-driven, manual patch, uninstall) is in Install and uninstall (complete).


What it does

Row element Behaviour
@file button (@folder on directories) Inserts @path (or @"path with spaces", @dir/ for directories) into the session's composer as an atomic file reference — the same chip the built-in @ completion and the built-in file drop produce. Falls back to copying the mention when no composer is reachable. Shown while the row is hovered or focused.
@file + Alt/⌥-click Always copies the mention to the clipboard.
date column YYYY-MM-DD HH:mm in local time; hover shows the full local date and time. Entries whose stat failed show nothing.
size column Regular files show a humanized size (870 B, 1.5 KB, 1.2 MB); the date's tooltip carries it, and the size's own tooltip gives the exact byte count.
sorting A header button cycles name → modified time → size → type; directories stay first, entries without the field sink to the end, and the mode is remembered (localStorage, best-effort).
adaptive columns The tree measures its own width in three tiers: ≥380px shows every column; 300–379px hides the size column; below 300px the chip collapses to a bare @ and the date to MM-DD HH:mm — nothing gets clipped in narrow sidebars.
runtime native fallback (R10) Right-click the Files tab → "Use the native file tree": instantly switches back to the native tree (no plugin uninstall needed); the same entry becomes "Use the enhanced file tree" to switch back. Note: Harness tabs have no ⋯ button — the entry point is the right-click menu; the enhanced tree's expansion state is not kept across the switch, and a restart starts enhanced.
reveal in file tree (R16) Right-click a file-preview tab → "Reveal in file tree": focuses the tree, expands the ancestor directories, scrolls to the row and briefly highlights it (same outline as the keyboard focus ring).
one-click @file from a preview (R16) Right-click a file-preview tab → "@file": inserts the previewed file's reference straight into the composer — exactly what the row's @ chip does, without going back to the tree to find that row.
where the entries appear Both entries are offered only in the right-click menu of file-preview tabs — not on the Files tab or other tabs. Harness tabs have no ⋯ button, so the entry point is the right-click menu.
git state on rows (R40a) A coloured dot after a file's name marks its work-tree state: untracked (green) / modified, unstaged (amber) / staged (blue), with a naming tooltip. At the bottom of the sidebar a git bar shows ⎇ branch ↑ahead ↓behind · latest commit summary (relative age); clicking it expands the commit list upward (the last 20 commits, newest at the bottom with a ● HEAD marker). Multiple repositories: when the workspace itself is not a repository but its first/second-level subdirectories are, the bar gains a repository selector — the picked repository drives the branch/commit info and the file dots. A broken repository (a .git without HEAD) shows in the selector as a disabled option, and the bar explains 「Not a valid git repository」 instead of vanishing. Outside a repository — or without a git executable — nothing renders; the state is read with the listing and cached for 30 s.
icons The host's own artwork, unchanged: FileTypeIcon + classifyFileType (category-coloured per file kind), the line-art folder icons, and GuideArtworkFiles for the guide capsule — the very components the native tree draws with. Inline glyphs take over only if those exports are unavailable.
header Workspace root path (full path on hover), an auto-refresh toggle, and a reload button.
rows Directories first, then natural case-insensitive name order — the native tree's own ordering.

Auto-refresh rides the Harness's own workspaceFiles.changes directory watch: a save in the workspace refreshes the open levels, with no polling.


Interaction

📁 src          @folder   2026-01-02 11:04
📄 README.md      @file    2026-01-01 09:12
  • The chip is hidden until the row is hovered or keyboard-focused; a click inserts the reference, Alt/⌥-click copies it, and the button then shows referenced / copied / failed for 1.4 s before returning to its noun label.
  • Times use a fixed YYYY-MM-DD HH:mm shape (no locale drift); the tooltip carries the full local time.
  • Directories expand level by level; reopening a level rereads it, and expansion plus scroll offset are remembered per tab — a file-preview round trip, or a runtime toggle to the native tree and back, leaves the tree as it was (a page reload starts over).

Keyboard and accessibility

Click any row once to move focus into the tree; after that everything is keyboard-only:

Key Behaviour
↑ / ↓ Move focus row by row across the visible rows (across levels)
→ Collapsed directory → expand; expanded → focus the first child row
← Expanded directory → collapse; file/child → focus the parent row
Home / End First / last visible row
Enter / Space Directory = expand/collapse; file = open in the sidebar
@ Insert that row's reference into the composer
Tab Leaves the whole tree in one step (roving tabindex — no per-row stops)

Accessibility semantics: the root list is role="tree", rows are role="treeitem" (with aria-level / aria-expanded), nested levels are role="group", and the search result count is announced through a polite live region. The reveal highlight matches the keyboard focus ring.


What stays native

  • Tab identity: still the files kind — the Mod+P shortcut, the guide page's "Workspace files" capsule and the tab title all come from the same registration.
  • Icons: the host's category-coloured FileTypeIcon, its line-art folder icons and its GuideArtworkFiles artwork are read from the running app — nothing is re-drawn or copied.
  • Opening: a row click still opens the file in the sidebar through dsh-resource://file/session/....
  • Ordering: directories first, then natural case-insensitive name order.

How it works

The native file tree has no per-row extension point (its row components are module-local and the package declares no row seat), so richer rows require supplying the body: a tab type registered at priority extension with the same kind (files) takes over the builtin, and its body/title are dispatched by the definition's own id — so there is no key collision.

No Client seam carries a modification time: workspaceFiles entries are { name, type, size? }, and the file version token is opaque by contract. The Host half therefore serves two authenticated routes of its own:

POST /api/dsh-at-sider/list     { sessionId, path }
  -> { ok: true, value: { path, root, entries: [{ name, type, mtimeMs, size? }], truncated } }

POST /api/dsh-at-sider/search   { sessionId, query }
  -> { ok: true, value: { query, matches: [{ name, path, dir, type, mtimeMs, size? }], truncated } }

Both are confined to the session's workspace root (the path is resolved against it and refused when it escapes) and are read-only (file contents are never read). The listing reads at most 2000 entries per level and caps stat concurrency at 32; the search walks the whole workspace, skipping node_modules/.git, never descending into symlinked directories, with a depth cap of 12 and 200 results, and stats only the matches.

Three further Client mechanisms: the action menu (sidebar.right.tab.menu.item, a list seat whose entries receive the tab they belong to plus dismiss) carries 「use the native file tree」, 「reveal in file tree」 and 「@file」; the runtime fallback dispose/re-registers the tab-type definition (the builtin resumes the moment it is unregistered); reveal hands the target path to the tree through openTab('files', { params: { reveal } }), and the body expands the ancestor chain and flashes the row from tab.navigation.params. docs/DESIGN.md records every alternative that was evaluated and rejected.


Known limitations

  • The builtin files body is shadowed, not composed: while this plugin is loaded the native tree does not render; uninstalling restores it exactly, and right-clicking the Files tab offers 「use the native file tree」 to switch back at runtime (no native code is modified either way).
  • Expansion and scroll offset are remembered per tab in a module-level memory, so preview round trips and the runtime toggle keep the tree as it was; what is not kept is the level data (a returning level is refetched) and anything across a page reload — the full slot-store migration is ROADMAP R30.
  • Git state comes from git status/git log subprocesses in the Host half (30 s cache, in-flight deduplication, timeout and output caps); outside a git repository nothing renders. A workspace that is a subdirectory of its repository maps status paths through --show-prefix; rename records count as their new path.
  • Modification times and the search both come from node:fs (the ctx.fs seam exposes no time field), so both routes carry their own workspace containment.
  • The Client half mirrors a few private details of the native tree (row ordering, the dsh-resource:// address grammar, the composer's reference-insertion call, the tab action-menu seat, openTab's navigation params and TabRecord.contentId). Each has a degradation path — a missing composer falls back to the clipboard, a missing watch falls back to the reload button, a missing menu seat simply means two fewer menu entries — and the version this was built and tested against is pinned in dsh.compatibility.
  • The plugin's inline glyphs only appear when the host artwork is unavailable.

Install and uninstall (complete)

Install

1. One command (official CLI, recommended)

dsh plugin --profile web add dsh-at-sider@latest
# or from GitHub: dsh plugin --profile web add github:aiyacharley/dsh-at-sider
# or from a local checkout: dsh plugin --profile web add /path/to/dsh-at-sider

2. Paste this to an agent

[Please install dsh-at-sider persistently (available in every session after a restart)]
1) Determine the DSH profile name (e.g. web; ask first if unsure).
2) Run: dsh plugin --profile <name> add dsh-at-sider@latest
3) Tell the user to restart DSH web (the Host half's listing and search routes register on boot).
4) After the restart, verify: hovering a row in the right sidebar's Files tab shows
   @file, the row's right edge shows a size and a modification time, the header has
   a search box, right-clicking the Files tab offers "use the native file tree", and
   right-clicking an opened file preview offers "reveal in file tree" and "@file".

3. Manual (optional)

  • Profile bundle layer: add "dsh-at-sider": "link:/path/to/dsh-at-sider" to dependencies in ~/.dsh/profiles/<name>/package.json, add "dsh-at-sider" to dsh.profile.bundles, run pnpm install, then restart.
  • Or merge the insert row from cordis.patch.yml into your own patch layer and restart.

Update

dsh plugin --profile web update dsh-at-sider@latest     # or @0.2.0 to pin

Restart DSH for the change to take effect.

Changed client.js during development and a restart "did nothing"? A client bundle is addressed by a rev derived from the file's metadata (mtime/ctime/size), and an old-rev request is rejected rather than served fresh bytes. Hard-refresh the browser first (Ctrl+Shift+R); if it is still stale, remove and re-add the plugin — a reinstall rescans the artifact and publishes a new revision, even without a restart. A link: install points at the working tree itself and has not gone stale; this is the revision-stamp layer. See the troubleshooting table in docs/INSTALL.md.

Uninstall

  • dsh plugin --profile web remove dsh-at-sider, then restart.
  • For a local link: install: drop the dependency and the dsh.profile.bundles entry, run pnpm install, then restart.
  • The native file tree returns exactly as it was.

Troubleshooting (installed but unchanged, empty date column, …) is in docs/INSTALL.md.


Development and tests

npm test          # node --test, 83 cases, fully offline (no network, no browser; git cases use a real git)
  • This plugin has no dependencies and no build step: client.js is the final browser module-loader artifact, index.js is the Host entry.
  • The suite loads client.js the way the module system does — inside new Function, with window, navigator and fetch injected — and drives the real components through a minimal React shim: containment, per-level listing and mtimeMs, both route contracts (listing plus the search skip list, result cap and depth cap), the takeover definition and its runtime toggle, menu-entry visibility, the @path grammar and address parsing, composer insertion with its clipboard fallback, the button labels, the host-artwork path and its fallback, the row box model, tree semantics (role/aria-level), the keyboard navigation branches, and a rendered-tree smoke test plus the quick-filter and reveal interactions.
  • Changes: CHANGELOG.md. Design: docs/DESIGN.md. Releasing: PUBLISH.md.

Version history

  • v0.2.0 — the bottom git bar (file rows coloured by untracked / unstaged / staged; a pinned bottom bar with the branch and the latest commit summary, expanding upward into the last 20 commits; full hash, author, and time on hover); multi-repository workspaces (when the workspace itself is not a repository but its first/second-level subdirectories are, the bar gains a repository selector — the picked repository re-anchors the state, and a broken .git shows as a disabled option with an explanation instead of vanishing); git state is read with bounded subprocesses and cached. Verified on a real multi-repo workspace; 83 offline tests.
  • v0.1.1 — fix: the tree's expansion survives the preview round trip now, like the native tree (remembered per tab in a module-level memory; the runtime toggle to the native tree and back keeps it too. Level data is still refetched on return and a page reload starts over — full migration is ROADMAP R30); the INSTALL troubleshooting table and the update sections document the client artifact's rev cache layer and the hard-refresh → reinstall ladder; the test shim scopes hook state per component instance; 66 offline tests.
  • v0.1.0 — first feature release: runtime native fallback (right-click the Files tab to switch between the enhanced and native trees — no uninstall needed); quick filter / locate (a header box searching the whole workspace recursively — skips node_modules/.git, 200 results — with @ chips on every result; click a file to open it, click a directory to jump back into the tree expanded); reveal in file tree (right-click a file preview to expand the ancestors and highlight the row); one-click @file from a preview (right-click a file preview to insert its reference straight away); keyboard navigation & a11y (arrows/Home/End/@, roving tabindex, role=tree semantics); 65 offline tests.
  • v0.0.4 — fixed a Files tab restored after a restart showing "no workspace directory" until a manual reload (the Host route now resolves the root through session persistence for cold sessions; a failed level self-heals with up to two spaced retries); size-column layout polish (right-aligned fixed box, two-space gap before the date, one right-pinned group); 52 offline tests.
  • v0.0.3 — new size column (humanized bytes + exact tooltip), sortable rows (name/modified time/size/type cycle, directories first, remembered preference), and width-adaptive columns (≥380px full / 300–379px no size / <300px bare @ + short time); 46 offline tests.
  • v0.0.2 — fixed the clipped modification-time column (the row is border-box again); file/folder icons are the host's own artwork again (FileTypeIcon + classifyFileType, IconFolder*Regular, GuideArtworkFiles), with the inline glyphs kept as a fallback; the README now follows the dsh-pubmed layout (Chinese primary + README_EN.md + npm/listing badges).
  • v0.0.1 — first release: takes over the files tab as an extension with an @file/@folder reference chip and a modification-time column per row; Host half serves /api/dsh-at-sider/list (workspace-confined, 2000 entries per level, 32 concurrent stats); 41 offline tests.

Per-version detail is in git tags; design notes are in docs/DESIGN.md; the roadmap (done/planned/milestones) is docs/ROADMAP.md.


Requirements

  • DSH 0.1.7-rc.2 (the design baseline) — re-verified on 0.2.0-rc.1 (CLI, dsh web) and 0.2.0-rc.2 (desktop app); all three are declared in dsh.compatibility. Either the web or the desktop profile works — put the profile actually in use after --profile.
  • Node.js ≥ 20 (the Host half uses node:fs/promises).

License

MIT. See LICENSE.

  • File-type classification and icons (FileTypeIcon, classifyFileType, IconFolder*Regular, GuideArtworkFiles) come from the DSH-shipped @deepseek-ai/dsh-client-ui-primitives and are read at runtime, not copied; the plugin's inline glyphs are only a fallback.
  • The tab takeover and its runtime fallback, the @path references, the modification-time and size columns, sorting and width adaptation, the quick filter and its search route, reveal-in-tree and the tab-menu entries, and the keyboard navigation with tree semantics are all original to this plugin.

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.