Skip to main content
A

dsh-enhancer

adamcjm/dsh-enhancer

Quality-of-life enhancements for the DSH workspace sidebar: hide workspaces and their sessions, reveal them from the view options, and open any workspace folder in Finder, Explorer or your file manager. Display-only: workspace, session and archive data ar

Install

dsh plugin --profile web add github:adamcjm/dsh-enhancer

README

@adamcjm/dsh-enhancer

npm version license

English | 中文

A DeepSeek Harness plugin that adds the small, everyday operations the shipped workspace menus are missing. It is deliberately named after the area rather than one feature, so new menu enhancements can be added without renaming the package again.

Today it ships four things:

WhereEntryWhat it does
Workspace row → "…" menuOpen folderOpens the workspace's directory in Finder, Explorer or your file manager
Workspace row → "…" menuHide workspace / Unhide workspaceHides the workspace and every session it owns from the sidebar
Sidebar → View optionsShow hidden workspaces (N)Reveals hidden workspaces again so they can be restored
Session row → "…" menuMove to TrashMoves the session's files to $DSH_HOME/trash, restorable from Settings → Plugins → Trash

Almost display-only by design:

  • no archive, unarchive, rename, move or pin side effects beyond what a user action asks for;
  • no changes to workspace records, session logs or the archive set;
  • opening a folder is read-only: it launches the OS file manager and nothing else;
  • moving a session to the trash never deletes it: the files are renamed into $DSH_HOME/trash/sessions/ and can be restored;
  • the only durable non-trash state is one small Host file ($DSH_HOME/storages/dsh-enhancer.json).

Install

dsh plugin --profile <profile> add @adamcjm/dsh-enhancer

Or wire it by hand in the profile's package.json:

{
  "dependencies": {
    "@adamcjm/dsh-enhancer": "^1.0.0"
  },
  "dsh": {
    "profile": {
      "bundles": ["…", "@adamcjm/dsh-enhancer"]
    }
  }
}

Restart the Host (or let the profile's live patch reload pick it up) and refresh the Web UI.

Local development

Point the profile at this checkout instead of the registry:

{
  "dependencies": {
    "@adamcjm/dsh-enhancer": "link:/absolute/path/to/dsh-enhancer"
  },
  "dsh": {
    "profile": {
      "bundles": ["…", "@adamcjm/dsh-enhancer"]
    }
  }
}

npm test runs both halves with zero dependencies: the browser half in a vm sandbox with a fake DOM, the Host half against a temp DSH_HOME and Node-style request/response fakes. The suite never launches the real file manager.

Usage

Open a workspace folder

Click the "…" button on any workspace row and choose Open folder. The Host resolves the directory from the workspace registry and launches:

PlatformCommand
macOS/usr/bin/open <path>
Windowsexplorer.exe <path>
Linuxxdg-open <path>

The entry reports failures in place instead of dismissing the menu silently:

  • Folder no longer exists — the registered directory is gone (status() is missing-dir);
  • Could not open — the launcher could not be spawned, or the platform is unsupported.

The page never sends a path. It sends only a workspace id, and the Host looks the directory up itself, so a stale row can never make the Host open an arbitrary folder. The registered path is the canonical fs.realpath of the directory at registration time, which means it is unaffected by renaming the workspace (rename only changes the display title).

Hide and restore workspaces

  1. Workspace row → "…" menu → Hide workspace — the row and all of its sessions disappear. While hidden rows are visible again, the same entry reads Unhide workspace.
  2. Sidebar → View options → Show hidden workspaces (N) — a persisted switch that reveals every hidden workspace; the label carries the current hidden count.

Hidden ids live on the Host in $DSH_HOME/storages/dsh-enhancer.json. The browser keeps no copy: it fetches the state once, then receives every change over an SSE stream (/dsh-enhancer/stream), so Desktop and dsh web (two Host processes sharing one $DSH_HOME) stay in sync without a refresh. Mutations travel as increments, so two clients hiding different workspaces cannot overwrite each other.

Move a session to the trash

Session row → "…" menu → Move to Trash.

  • A running session is refused: the dialog says so and offers only Close. Stop the session first.
  • A session with child sessions (sub-agents) asks what to do:
    • Delete only this session — the parent leaves, the children stay in place as orphans (their parentSession now points at a session that no longer exists);
    • Delete with N child sessions — the parent and all of its children move together, and restoring the parent restores them too.
  • A session whose files cannot be located is reported and nothing is touched — the plugin fails closed rather than guessing a path.

Files move to $DSH_HOME/trash/sessions/<timestamp>_<sessionId>/data/, beside a manifest.json that records the title, workspace, cwd, original relative path, size and the child entries created by the same deletion.

Restore from Settings → Plugins → Trash. The tab lists every deleted session with its title, workspace, age, size and child count. Tick the entries you want, then:

ButtonScopeConfirmation
Restore (N)the ticked entries onlynone
Delete permanently (N)the ticked entries onlyyes, "cannot be recovered"
Empty trash (N)the whole trash, ignores the selectionyes, "empties everything"

Restoring a parent also restores the child entries created with it. Restored sessions are re-attached to their original workspace when it still exists, otherwise re-grouped by their cwd. Empty trash only removes $DSH_HOME/trash/sessions/* — it never touches DSH's own sessions/ directory.

How it works

  • Hiding never touches the DOM. The plugin wraps the workspaces.list client model (getSnapshot/subscribe) and projects a filtered snapshot, so the sidebar, its search results and the Hero workspace picker all hide the workspace from one place. Sessions belonging to a hidden workspace are unioned into a view-only archived set so the shipped grouping cannot trail them into "Ungrouped" — nothing is written to the archive.
  • Menu entries are DOM additions. The shipped Workspace row menu and View options menu are hardcoded arrays with no slot in DSH 0.2.1-rc, so the plugin appends entries that clone the shipped item's own classes. A MutationObserver finds the menu portal after it opens. The session menu entry and the trash tab are real slots (sidebar.workspaces.session.menu.item, settings.plugins.tab, shell.overlay), not DOM injections.
  • The Host half owns the durable state and the open-workspace action. Opening is a detached spawn with stdio: 'ignore', so the HTTP request resolves as soon as the launcher exists.
  • Trashing never edits DSH's storage directly. The Host first drops the durable references through the public registry API (detachSession, unarchiveSession, unpinSession), then renames the session directory into the trash and writes the manifest. A session is located by matching the <sessionId> entry name inside $DSH_HOME/sessions/<group>/, so a change in DSH's path encoding or a switch to a single-file layout fails closed instead of deleting the wrong thing.

Upgrading from @adamcjm/dsh-workspace-visibility

This package replaces it. The hidden-workspace feature is identical, and the first start adopts the old state file once, so your hidden list survives the upgrade.

dsh plugin --profile <profile> remove @adamcjm/dsh-workspace-visibility
dsh plugin --profile <profile> add @adamcjm/dsh-enhancer

Migration details:

  • $DSH_HOME/storages/workspace-visibility.json is read once, copied to dsh-enhancer.json, and left in place so a rollback still works.
  • Do not keep both packages enabled at the same time: each one injects its own menu entries, so the workspace menu would show duplicates.

Compatibility

  • DSH 0.2.1-rc or newer (workspaceRegistry Host service, workspaces.list client model, locale service, webServer route carrier).
  • Node.js 20+ in the Host process.
  • macOS, Windows and Linux. "Open folder" requires a desktop session; on a headless Host the route answers with open-failed.
  • The Host that serves your page is the one that opens the folder. With a remote Host, the file manager opens on the remote machine (or fails), never on your laptop.

License

MIT

Related plugins