dsh-plugin-sidebar: workspace/session browsing on the left, file & Git panel on the right

๐Ÿ—‚๏ธ A left & right sidebar for DeepSeek Harness
Left ยท workspace/session browsing โ€” Right ยท file + Git panels

Highlights ยท Install ยท Development ยท Architecture ยท License ยท ไธญๆ–‡

--- > ๐ŸŒ **็ฎ€ไฝ“ไธญๆ–‡**: ่ง [README.md](README.md) ยท English in this file # dsh-plugin-sidebar Make DeepSeek Harness **session management feel like an IDE**. Browse every session by workspace on the left (status dots, groups, search, one-click actions); browse files and review Git changes in-place on the right (commit, switch branches). Install and go โ€” styling follows the DSH theme and adapts to light/dark. ## โœจ Highlights ### ๐Ÿงญ Left sidebar ยท workspace/session browsing - **Grouped by workspace**: collapsible group headers (folder icon + title + count + chevron), active session highlighted - **Session status at a glance**: running (green pulse) / waiting (amber) / done / idle โ€” status lane + title chip - **Hover quick actions**: rename (inline) / fork / archive / **move to folder** - **Top search**: local title/cwd/workspace match merged with remote content search (250 ms debounce), `Esc` to dismiss - **Group-hover actions**: new session here / rename workspace / delete workspace - **Rail mode**: sidebar collapses to an icon column; search/new auto-expand ### ๐Ÿ“ Right sidebar ยท file browsing + Git panel - **Session-header toggle** (next to the session log download); **toggle state is remembered per session** โ€” a session you opened the panel in stays open after switching away and back - **File explorer**: lazy directory tree (expand to load children), name search, content search (file/line/text), **directory git-status aggregation badges** (highest priority D>M>A>R>U of changed files underneath) - **File preview tab (CodeMirror editing)**: a **File preview** tab after the main region's **Conversation / Trajectory** tabs; clicking a file in the right sidebar auto-switches there. Text files render with **CodeMirror 6** โ€” **syntax highlighting** (language auto-picked by extension) + **editable** + **save back to disk** (`โ—` unsaved marker + save button, head pinned so it's always visible); binary/large files show a notice - **Git panel**: status split into **Staged / Changes** (`--porcelain=v1 -z` NUL parsing), colored diff, commit box, branch switch - **Bulk actions (VSCode Source Control style, on section-header hover)**: `Stage all`(+) / `Unstage all`(โˆ’) / `Discard all`(undo); per-file row hover: diff(file) + stage(+)/unstage(โˆ’) + discard(undo) - **Commit history (VSCode Source Control style)**: timeline dots + connector line, one-line-truncated message; expand to reveal author ยท date + that commit's **changed-file list** (M/A/D colored letters), files lazy-loaded on demand ![Right sidebar file explorer + file preview tab](assets/file-explorer.png) ![File preview: editing README in CodeMirror (syntax highlighting + save)](assets/preview-editor.png) ![Right Git panel + VSCode-style commit history](assets/git-panel.png) ### ๐Ÿ”€ Move session to folder - Copy semantics (fork): the source session stays; a new session is created in the target workspace inheriting **all completed history**, auto-opened after creation - Uses the official `agents.create(seed + meta.cwd)` path; the new session is an **agent** (lifecycle owned by the agent registry, not removed when the plugin stops) - Target-workspace picker: `createPortal` to body + `position: fixed` anchored, escaping list `overflow` clipping, flips upward near the viewport bottom ### ๐ŸŒ Polished for Chinese users - Full Chinese UI + English, follows the DSH locale automatically - Uses only DSH theme tokens (`--dsw-alias-*` / `--ds-*`), adapting to light/dark ## ๐Ÿ“ฆ Install Install from GitHub or a local path. **First confirm your dsh version, then pick the plugin version that matches it per the Version Compatibility table below**: ```bash # From GitHub (recommended; pin the version to match your dsh) # Note: pnpm's GitHub version-lock syntax uses `#tag`; the old `@0.3.2` form is mis-parsed by pnpm as the nonexistent repo null/0.3.2 dsh plugin --profile web add github:webkong/dsh-plugin-sidebar#v0.3.2 # Or install the latest main branch dsh plugin --profile web add github:webkong/dsh-plugin-sidebar#main # Or from a local path (development) dsh plugin --profile web add /path/to/dsh-plugin-sidebar ``` After restarting `dsh web`, the left sidebar takes over `sidebar.workspaces`, the right sidebar takes over the `details` column, and a panel toggle appears in the session header. > โš ๏ธ **How it takes effect**: install / restart; **Host changes require a `dsh web` restart**, while **Client changes (`lib/client.js`) only need a page refresh**. > ๐Ÿ’ก **Install recommendation**: > - `dsh โ‰ฅ 0.1.2` (incl. `0.1.2-alpha.3`): **use `0.3.2`** โ€” it adapts the dsh 0.1.2 client service migration and lazy resolution; > - `dsh 0.1.1-rc.x`: use `0.3.2` or `0.3.1`; > - older dsh: fall back to `โ‰ค 0.2.x` (but on `dsh โ‰ฅ 0.1.2` the older client never loads). ## ๐Ÿ”– Version Compatibility **Quick reference: your dsh version โ†’ recommended plugin version** | Your dsh version | Compatible plugin versions | Recommendation | | --- | --- | --- | | **dsh โ‰ฅ 0.1.2** (incl. `0.1.2-alpha.3`) | **0.3.1 or newer** | **Recommended `0.3.2`**: adapts the dsh 0.1.2 `uiWorkspace` service migration + lazy resolution | | **dsh 0.1.1-rc.x** | 0.3.0 โ€“ 0.3.2 | Either; `0.3.0` verified on `0.1.1-rc.2` | | **dsh โ‰ค 0.1.1-rc.x** | โ‰ค 0.2.x | Older; on `dsh โ‰ฅ 0.1.2` the client never loads (waits for the removed `dsh-client-runtime`) | **Detailed: plugin version โ†’ compatible dsh versions** | Plugin version | Compatible dsh versions | Notes | | --- | --- | --- | | **0.3.2** | dsh โ‰ฅ 0.1.1-rc.2 | Left-panel hierarchy styling: the status dot left of a session title is indented rightward to emphasize the `folder โ†’ session` parent-child level; the session title font size is reduced from 13px to 12px | | **0.3.1** | dsh โ‰ฅ 0.1.1-rc.2 (verified on `0.1.1-rc.2`, `0.1.2-alpha.1`, `0.1.2-alpha.3`) | Adapted to the dsh 0.1.2 client service migration: `startSession` / `pickDirectory` now go through the **`uiWorkspace`** service (formerly on `workspaces`), and `layout`/`sessions`/`workspaces`/`uiWorkspace`/`timer` are **lazy-resolved at call time** (services activate asynchronously; no longer cached at apply time). Fixes "right-panel toggle no response" and "left add-folder no response" | | **0.3.0** | dsh โ‰ฅ 0.1.1-rc.2 (verified on `0.1.1-rc.2`, `0.1.2-alpha.1`) | Removed `dsh-client-runtime` from the client `inject` list (removed in dsh 0.1.2); **not adapted** to the 0.1.2 `uiWorkspace` migration, so some clicks may be unresponsive on `0.1.2-alpha.3` | | **โ‰ค 0.2.x** | dsh โ‰ค 0.1.1-rc.x | Older; on `dsh โ‰ฅ 0.1.2` the client never loads while waiting for the removed `dsh-client-runtime` | | โ‰ค 0.2.x | โ‰ค 0.1.1-rc.x | On dsh โ‰ฅ 0.1.2 older clients never load while waiting for the removed `dsh-client-runtime` | ## ๐Ÿ”ง Development ```bash npm install # esbuild / typescript (dev only) npm run build # esbuild src/ โ†’ lib/ (Host ESM + Client __ModuleLoader__ bundle) npm run typecheck # tsc --noEmit strict npm test # node --test pure-function unit tests (42 cases) npm run check # typecheck + bundle syntax checks + unit tests ``` ### Structure (TypeScript-modular) Source is organized TypeScript-modular (modeled on the official UI plugins); build output lives in `lib/`: ``` dsh-plugin-sidebar/ โ”œโ”€โ”€ package.json # dsh.bundle / dsh.client declarations, scripts โ”œโ”€โ”€ cordis.patch.yml # bundle patch: mounts the dsp-sidebar row โ”œโ”€โ”€ build.mjs # esbuild build (Host ESM + Client __ModuleLoader__ bundle) โ”œโ”€โ”€ tsconfig.json # strict typecheck (node + DOM/React) โ”œโ”€โ”€ lib/ # build output (gitignored) โ”‚ โ”œโ”€โ”€ index.js # Host single-file ESM bundle โ”‚ โ””โ”€โ”€ client.js # Client __ModuleLoader__ bundle โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ host/ # Host source (Node env) โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # entry: name/inject/apply + webServer route registration โ”‚ โ”‚ โ”œโ”€โ”€ session.ts # session copy (move to folder): readSession โ†’ cut โ†’ agents.create(seed+meta.cwd) โ”‚ โ”‚ โ”œโ”€โ”€ git.ts # git ops: runGit + porcelain/NUL/log parsing โ”‚ โ”‚ โ”œโ”€โ”€ fs.ts # fs ops: listDir / readText (512 KB truncation) โ”‚ โ”‚ โ”œโ”€โ”€ search.ts # search: name recursion + content line matching โ”‚ โ”‚ โ””โ”€โ”€ http.ts # JSON responses / loopback check / body / escaping โ”‚ โ””โ”€โ”€ client/ # Client source (DOM + React env) โ”‚ โ”œโ”€โ”€ index.ts # apply entry: inject styles / register dictionaries / register slots โ”‚ โ”œโ”€โ”€ api.ts # /dsp-sidebar/api fetch wrapper โ”‚ โ”œโ”€โ”€ i18n.ts # zh/en dictionary (NS + key types) โ”‚ โ”œโ”€โ”€ types.ts # contracts: session/workspace data plane + Host API + git wire shapes โ”‚ โ”œโ”€โ”€ util.ts # shared helpers: relative time / basename / status derivation โ”‚ โ”œโ”€โ”€ icons.tsx # icons (lucide-style stroke + right-panel filled glyph) โ”‚ โ”œโ”€โ”€ previewStore.ts # file-preview shared store (sessionId-scoped, useSyncExternalStore) โ”‚ โ”œโ”€โ”€ preview/ # main-region "File preview" view โ”‚ โ”‚ โ””โ”€โ”€ PreviewView.tsx # conversation.view occupant: subscribes to previewStore โ”‚ โ”œโ”€โ”€ styles/ # CSS split by component domain + aggregate injection โ”‚ โ”‚ โ”œโ”€โ”€ left.css # left sidebar styles โ”‚ โ”‚ โ”œโ”€โ”€ right.css # right sidebar styles โ”‚ โ”‚ โ”œโ”€โ”€ preview.css # main-region preview tab styles (global dsw tokens) โ”‚ โ”‚ โ””โ”€โ”€ index.ts # injectStyles (idempotent single style tag) โ”‚ โ”œโ”€โ”€ left/ # left sidebar (aligned with official WorkspaceBrowser + rows/) โ”‚ โ”‚ โ”œโ”€โ”€ derive.ts # data derivation: grouping / search merge โ”‚ โ”‚ โ”œโ”€โ”€ rows.tsx # row components: SessionCard / SearchRow / GroupSection (incl. move portal) โ”‚ โ”‚ โ””โ”€โ”€ WorkspaceBrowser.tsx # main component: header + search + list + rail โ”‚ โ””โ”€โ”€ right/ # right sidebar (aligned with official RightSidebar + SourceControl + FileExplorer) โ”‚ โ”œโ”€โ”€ derive.ts # git status classification (badge / staged / unstaged / untracked / dirBadge) โ”‚ โ”œโ”€โ”€ FilesPanel.tsx # file browser (lazy tree + search + preview + git badges) โ”‚ โ”œโ”€โ”€ GitPanel.tsx # git panel (status / stage / diff / commit / timeline history / branch) โ”‚ โ””โ”€โ”€ RightPanel.tsx # panel shell (activity bar + tab switch) + header toggle โ””โ”€โ”€ test/ # pure-function unit tests (node --test) ``` ## ๐Ÿ“ก Communication contract The Host exposes an HTTP API via a `webServer` prefix route `/dsp-sidebar/api` (loopback only, POST, method name as the last path segment); the Client calls it with browser `fetch`: | Method | Description | | --- | --- | | `fs.list` | list dir (`{path}`) | | `fs.read` | read text (512 KB truncation; binary returns `kind:'binary'`) | | `fs.search` | search (`{mode: 'name'\|'content', path, query}`) | | `fs.gitStatus` | dir git-status map (path โ†’ XY, for file badges) | | `git.status` | git status (`{cwd}`) | | `git.diff` | diff (`{cwd, path?, staged?}`) | | `git.stage` / `git.unstage` | stage / unstage (`{cwd, path?}`) | | `git.discard` | discard changes (`{cwd, path}`) | | `git.commit` | commit (`{cwd, message}`) | | `git.log` | commit history (`{cwd, count?}`) | | `git.logFiles` | changed files of one commit (`{cwd, hash}`, name-status parsed) | | `git.branches` | branch list (`{cwd}`) | | `git.checkout` | checkout branch (`{cwd, branch}`) | | `session.copyTo` | copy session to target workspace (`{srcId, targetPath}`) โ†’ returns `{sessionId}` | ## ๐Ÿ— Architecture - **Host** (`src/host/`): registers the `/dsp-sidebar/api` prefix route via `webServer`. File ops go through the mounted `fs` service (`resolve` โ†’ `listDir`/`stat`/`readText`/`readBytes`, respecting sandbox & observation policy); git ops go through the mounted `shell` service (`resolve` + `run`, `git -C ` + porcelain/NUL parsing โ€” the same execution path as the official bash tool) - **Client** (`src/client/`): calls the Host via `fetch('/dsp-sidebar/api/...')`; registers slots (`sidebar.workspaces` / `details` / `conversation.session.header.utilities` / `conversation.view`); session copy uses Host services like `sessionQuery` / `workspaceRegistry` / `agents` (lazy resolution โ€” these activate asynchronously, so `ctx.get` at call time rather than cached at `apply`) ## ๐Ÿ“„ License [MIT](LICENSE)