Releases: onetest-ai/dsh-desktop
Release list
v0.7.2
Hotfix correcting the file-open behavior shipped in 0.7.1.
Fixes
- The deliverable card's three file actions now behave as intended:
- Open → opens in the app's own web/editor pane (
.htmlrendered in the web pane, everything else in the editor), instead of the harness's built-in side tab. - Open in default app → opens in the OS default application (0.7.1 wrongly sent this to the in-app pane).
- Show in Finder → reveals in Finder.
Requires@onetest/dsh-desktop-pane@0.2.4.
- Open → opens in the app's own web/editor pane (
Signed with Developer ID and notarized. Existing installs auto-update.
v0.7.1
Hotfix for two bugs in 0.7.0.
Fixes
- File "Open" no longer escapes to the system browser. The harness's "Open" on file cards opened files with the OS default app (an
.htmldeliverable launched your browser). It now opens in the app's own panes —.htmlin the web pane, everything else in the editor — falling back to the native open for files outside an open project. (Shell half of #8; pairs with@onetest/dsh-desktop-pane@0.2.3.) - Restored the macOS Dock icon. The desktop pet's window flipped the whole app to accessory mode (
visibleOnFullScreen), silently dropping the Dock icon in 0.7.0. The pet keeps its cross-space/fullscreen behavior and the Dock icon is back. (#9)
Signed with Developer ID and notarized. Existing installs auto-update.
v0.7.0 — the desktop pet
The desktop pet
A floating, always-on-top companion that reacts to the agent in real time — and a set of fixes to how the app delivers harness hooks, which also repairs turn-completion notifications.
The pet
- Reacts to real agent activity with status bubbles:
Thinking… → Reading auth.ts / Running npm / Searching "…" → Waiting for you… → Done.(with a wave). Bubble text is templated from tool metadata only — never prompts or model output. - Click-to-compose: type into the pet and it starts/continues a turn in the harness through its real session API, with a harness-style compose bar (workspace picker, current mode/model, inline send).
- Missed-turn bell, drag-to-move with a remembered position, reduced-motion support, light/dark aware.
- Uses the Petdex sprite format — install pets with
npx petdex install <slug>; the app discovers them from~/.petdex/pets/. - Opt-in: enable and pick your pet from Settings → Pet or the tray.
Hook delivery fixed (affects more than the pet)
Turn-completion notifications and the pet both depend on the Claude Code hook bridge. Several bugs meant hooks could silently never fire:
- The generated hook script never ran from paths containing a space (
Application Support). - The hook bridge was dropped entirely whenever a custom
pluginslist omitted it — it is now guaranteed. - The bridge was installed by bare name, resolving npm's stale
latesttag: an incompatible build that also broke the harness'sask_user_questiontool. It is now pinned to the managed harness version, and a stale stored entry is re-pinned automatically. - The hook bridge now ships as a visible default plugin, so it appears in the Plugins tab and existing installs pick it up on next launch.
Install
Download the .dmg below (Apple Silicon, signed and notarized). Existing installs update themselves.
v0.6.0 — harness in $DSH_HOME, hide built-in sidebar, GitHub plugin installs
Verified against DeepSeek Harness 0.1.5 (a managed source tracks latest on its own).
What's new
- Managed harness runs in
$DSH_HOME. The project you work in is chosen from the harness's own Workspaces, so switching projects no longer restarts the harness. The Workspace folder setting is gone (existing configs stay valid). - Hide the harness's built-in right sidebar (off by default). This app has its own file tree / git / tasks rail, so the harness's redundant sidebar — dock and expand button — is hidden. Its plugin stays loaded (the chat UI needs its service); only the UI is hidden. Every other header control (the "…" menu, download, open-in) is untouched.
- Editor previews. Markdown opens rendered by default; CSV/TSV render as a read-only table (papaparse: quoting- and newline-safe, sticky header, zebra rows); HTML files get an "Open in Web" button.
- Install plugins from public GitHub repos —
github:owner/repoorgithub:owner/repo#ref, from the Plugins tab or a hand-editeddesktop.json. The ref is pinned to a commit, the real package name discovered, and the build verified, so a repo that ships no build fails loudly instead of loading broken. Install progress now streams and rows show "Installing…". - Settings window no longer flashes see-through during a harness restart.
Signed with Developer ID and notarized. macOS may still prompt on first launch; right-click → Open.
v0.5.0 — app auto-update
What's new in 0.5.0
App auto-update
The app now updates itself over GitHub Releases. When a newer version is published, it downloads in the background and offers Restart to install — in the tray, a native notification, and a new Settings → Updates tab (with the app version and a "Check for updates" / "Update and install" control).
This is the first release that carries the update feed (
latest-mac.yml+ the mac zip). 0.4.0 and earlier can't detect it, so update to 0.5.0 by hand this once; from here, future releases arrive automatically.
Per-project MCP bridge → @onetest/dsh-project-mcp-bridge
The default project MCP bridge moved to the @onetest scope (a bugfixed build) and now floats to latest. A one-time startup migration rewrites an existing install from the old unscoped dsh-project-mcp-bridge automatically — keeping any per-entry config, deduping, and never reinstating a bridge you removed.
Install: download the dmg below (Apple-notarized, Apple Silicon).
🤖 Generated with Claude Code
v0.4.0 — the task board
DeepSeek Harness 0.4.0
Task board
A file-backed task board on the Obsidian model, under .dsh/tasks/ in the workspace — the files are the board, git is the history.
- Three types — workitem (campaign / mission / task), bug, test — as markdown with YAML frontmatter; children are folder-derived.
- Five statuses — idea → backlog → executing → validation → done.
- Two views — a side-column tree that navigates, and a swimlane board in the content panel where campaigns and missions fold.
- An editable in-board detail (not a modal): read an entity as a document, edit its prose inline, add acceptance criteria, and attach document and test links — all without leaving the board.
- A tests destination, so a test is reachable without knowing it exists.
- The agent drives the same board through eight MCP tools.
- Visual language grounded in Linear: a status glyph that fills as work moves, verdict dots, a dark-canvas / raised-card board, every colour a theme token.
Git source-control panel
- Read the working tree, stage/unstage/commit, and fetch / pull / push with the remote, including the credential-failure surface (#2, #3).
Under the hood
Every board write goes through one store that resolves each path against a security boundary and writes atomically; the legacy .yaml → .md conversion is hardened against content loss. ~2100 tests.
Full history: v0.3.0...v0.4.0
v0.3.0
A terminal in the split, Settings rebuilt around rows rather than stacked cards, and the agent's view tools split into one MCP server per surface — mcp__app_browser__click, mcp__app_editor__open_file.
Most of the fixes below are things only the running app showed: a rail that vanished for good once the window had ever been zoomed, a terminal panel that reopened empty, and dividers with no gap left to grab.
Added
-
The terminal has a keyboard shortcut. ⌘⌥J, alongside ⌘⌥B for the tree and ⌘⌥W for the browser. The rail's button has advertised that key in its tooltip since the panel shipped, but nothing registered it: an accelerator exists only where a menu item carries one. The rail and the menu now open the panel through one function, so neither can forget to tell the page to start a shell.
-
A terminal panel with tabs, opened from the foot of the rail.
+opens another shell, each tab closes itself, and the✕at the end of the strip closes the panel with every shell in it — while the rail's button only hides it, so shells left running are still there on the way back. Each tab owns its terminal rather than sharing one and swapping buffers, so a switch keeps scrollback, selection, and cursor. It runs your login shell — or the one named under Settings → Advanced → Terminal shell — in the workspace the tree is showing, and keeps that directory: switching workspace later moves the tree, not a shell you are already working in. It takes the editor's place when the editor is closed, the whole split when the tree is closed too, and docks along the bottom (up to half the window) when both are up.node-pty runs in its own utility process rather than in main, the arrangement VS Code moved to after node-pty crashes took down whole windows and busy terminals blocked their event loops; here that process holds the harness views, the MCP server, and the project watcher. Output is flow-controlled on their watermarks (100000/5000/5000), so
caton a large file cannot outrun the panel drawing it.node-pty's published tarball ships
spawn-helperwithout its executable bit, and every pty spawns that binary. The build repairs it before packaging — a signed bundle cannot be repaired afterwards — and a smoke test asserts it on the packaged app.
Changed
-
DevTools is left out of packaged builds. It stays in a development run, where it is ours to use.
-
Settings is two columns and flat: the sections in a list down the left, the section itself on the right, grouped by hairlines rather than by boxes. Cards inside cards inside cards spent attention on the borders instead of the settings. A strip across the top worked at five sections and hid four of the five behind a horizontal scan every time. Each setting is now a row — what it is on the left, the control on the right — with the note that used to hide behind an ⓘ shown in full. The MCP tab's two scopes, global and per-project, each became a card carrying its own name, its file, and its own ways of adding a server; they previously appeared as five flat groups with identical controls and nothing saying which file each wrote to.
Fixed
-
The rail comes back when the window has been zoomed. ⌘+ zoomed this app's own chrome, and Chromium keeps a page's zoom per origin and restores it on every load — so one press moved the rail to x=1250 inside a page only 1168 wide, off its own right edge, and left it there across every relaunch. The rail's own numbers all looked right, which is why it read as the buttons having vanished. The window's page is now pinned to actual size, and the zoom items act on the harness, which is the view with anything to read in it.
-
The terminal panel comes back with a shell in it. Its page is loaded with the window and starts a shell once, at load — so closing the last tab (which takes the panel with it) left the rail's button reopening a strip of chrome with nothing in it, for as long as the window lived. Opening the panel now tells the page, which starts a shell when it has none and otherwise puts the keyboard back in the terminal that was showing. A panel opened in the first moments after boot waits for the page to finish loading rather than having the message dropped.
-
The rail and the dividers are placed again once their page has loaded. They have no position in
shell.css— they are placed only by theshell:placesmessage — and the first layout pass runs whileshell.htmlis still loading, where a page drops what is sent to it. Until now it worked only because a later pass (a theme or project event during boot) happened to re-send in time. -
A terminal repaints after losing its GPU context. Disposing the WebGL renderer hands drawing back to xterm's DOM renderer, which starts from an empty screen: a macOS fullscreen transition takes the context away at a moment nothing is being written, so the panel stayed blank until the next keystroke.
-
One MCP server per surface, and the tool is a bare verb:
mcp__app_editor__open_file,mcp__app_browser__click. The harness publishes every tool asmcp__<server>__<tool>, so the server segment already says which surface a tool belongs to; a single server withbrowser_/editor_prefixes on each tool paid for that twice and buried the verb at the end of a long name. The three ways of getting at a page now say what each gives back —opennavigates and returns the text,readreturns the text of whatever is showing,snapshotnumbers the controls — wherebrowse_page,read_open_pageandbrowser_read_pagewere near-twins a model guessed wrong. -
The view tools are named
mcp__desktop_views__…, notmcp__desktop-views__…. The harness publishes each MCP tool asmcp__<server>__<tool>and a hyphen is legal there, so the old server name produced one hyphen in a name that is otherwise all underscores — which a model normalizes, calls, and is told is an unknown tool. Observed happening in a real session. -
Settings rendered white, with its content scrolling through the fixed header: it mapped the harness's tokens on
:root, one level above thebodythe vendored sheet defines them on, so every derived variable resolved to nothing.
v0.2.0
The first build to carry everything below. v0.1.0 stored MCP tokens in the OS keychain and met users with a login-password prompt; this release stores them in a file at mode 0600 instead, and adds the side pane, the browser the agent can drive, and the file tree.
Added
-
A side pane beside the harness, laid out like an IDE mirrored: the conversation on the left, an editor in the middle, a file tree on the right, and a rail of buttons at the outside edge. Each column is draggable and remembered between launches. The editor is Monaco, appears when a file is opened rather than sitting empty, and holds a tab per file — each with its own document, so switching keeps scroll position and undo history. Files are read and written only inside a project the harness has opened, and only as text under 2MB.
-
A browser in the pane, with back, forward, reload, and an address bar that takes a bare host — a real page in its own process, with no preload of its own. The agent reads through it (
browse_page,read_open_page) and drives it through the Chrome DevTools protocol:browser_read_pagenumbers the page's controls, andbrowser_click,browser_type,browser_press_key,browser_select_option,browser_drag,browser_upload_file,browser_handle_dialogs,browser_evaluate,browser_read_console,browser_resize, andbrowser_screenshotact on them. Input goes through the protocol rather than through injected JavaScript, so the page receives it as the user's own — which is what native dialogs, file inputs, and drag-and-drop libraries require. Playwright's own MCP server drives a separate headless browser; every description here says which browser it means. -
The browser waits, and says what happened behind the action.
browser_wait_forwaits for an element or some text to appear or go, or — naming neither — for the time to pass, which is how a timed dialog is waited out and reported. Every action reports any dialog the page opened and any page the browser moved to on its own.browser_dragtakes a pixel distance as well as a target, for a resize handle where the distance is the point. Editing shortcuts carry the command the browser's editor acts on, soMeta+aselects rather than merely being pressed.window.promptis replaced with one that answers from the dialog policy, because Electron does not implement it and a page calling it otherwise throws. -
The browser is driveable from the moment it exists, rather than from the first tool call that needs it. A page opens dialogs on its own — on load, on a timer, on a link the user followed — and one that opens while nothing is attached is never answered: it blocks the page and every call after it. The attachment is also re-established if something else takes it, such as the developer tools.
-
Every browser action waits for its target to stop moving, because a page still loading adverts moves its own controls by more than the height of one — and a click at a point measured a moment earlier lands on the control above the one that was asked for.
-
The file tree follows the disk. A project is watched while it is open, so a file the agent writes appears without a reload. Changes are collected before the tree is redrawn, and
.gitandnode_modulesare ignored, so an install does not flood it. -
File-type icons for JSON Lines, and the solid folder for an expanded folder: vscode-icons draws the plain open folder as a hollow outline, which at sixteen pixels reads as an empty box beside the folders above it, and the twisty already says whether a folder is open.
-
This app's surfaces use the harness's own design tokens and follow the harness's own Appearance setting, live: set dark in the harness and these columns turn dark with it. The token sheets are vendored (see
vendor/dsh-theme/README.md) because the published theme package no longer ships them. -
An update to the harness is reported in the tray, found at startup rather than when the Settings window happens to be opened.
-
View tools for the agent, served over MCP on loopback while the app runs: show a file in the editor, open a page in the Web tab, show a change it proposes before making it, and read what you have selected. Nothing is written to
mcp.json— the entry is synthesized per boot, so it cannot go stale when the app is not running. Switchable off on the MCP tab. -
A startup splash that runs a healthcheck, installs any missing plugin with its output visible, and hands off to the harness — replacing a first launch that reported its own defaults as failures.
-
@onetest/dsh-desktop-pane, a harness plugin shipped by default, putting the file tree's toggle at the foot of the harness's own sidebar. It renders only inside this app. -
Per-project MCP servers are managed in Settings, not just by hand: the MCP tab lists the projects the harness has opened and edits each one's
<project>/.dsh/mcp.jsonwith the same presets, paste field, and controls as the global list. -
The login shell's
PATHis resolved and cached, so tools installed through nvm, Homebrew, or similar are reachable to the agent even though a Finder-launched app inherits only/usr/bin:/bin:/usr/sbin:/sbin. Resolution uses an interactive login shell, because version managers initialize in.zshrcrather than.zprofile; it costs about 2.6 seconds, so the result is cached and refreshed in the background rather than resolved on every launch.pnpmPathandnpmPathstill work and are still honoured, but are usually no longer necessary — and unlike them, a resolved PATH self-heals when a version manager upgrade moves the toolchain. -
Extra PATH entries on the Advanced tab, an override for a machine where shell resolution fails.
-
MCP servers are configured in
~/.dsh/mcp.json, in the standardmcpServersformat other MCP clients use — so a block from any server's README can be pasted unmodified, and one copied out still works elsewhere. The tab gains a paste field, an add-by-hand form, and presets for Playwright, Filesystem, and Memory alongside the existing remote ones. -
Local (stdio) MCP servers are supported, launched as
npx,uvx,docker, or any command. Theirenvvalues are carried to the server through the harness child's environment, never written into the generated overlay, which is world-readable. -
Per-project MCP servers, via
dsh-project-mcp-bridgeshipped as a default plugin (pinned). Drop a.dsh/mcp.jsoninto a project and every session opened there gets those servers — each connected per session, with its working directory set to that session's own, so a server like Playwright writes its artifacts into the project it belongs to instead of wherever the app happened to launch. Servers configured in the MCP tab remain global and shared, and win a name collision unless the project entry sets"override": true. -
A local MCP server is started before it is saved, with its own output shown live. The harness allows a server 60 seconds to list its tools and does not expose that bound, so an
npxserver whose first run downloads its package could mount with zero tools and no error; doing the download at add time means the harness always meets a warm cache. The probe has no time limit — one would reintroduce the failure it prevents — and a server that will not start is not written, with an "Add it anyway" override for the cases where the probe is wrong. -
The preset catalog is data, at
assets/mcp-presets.json, with~/.dsh/mcp-presets.jsonmerged over it by id — so a wrong endpoint or a team's own servers can be fixed without an app release.
Changed
- MCP tokens are stored in cleartext instead of the OS keychain, in
~/.dsh/desktop-secrets.jsonat mode0600— the approach.mcp.json,~/.aws/credentials,~/.npmrc, and theghCLI take. A Keychain item's ACL trusts specific signed binaries, so every re-signed build, every bundle-id change, and every separate copy of the app raised its own login-password prompt; ordinary users met that dialog several times before the app worked. The tradeoff is explicit: other accounts on the machine cannot read the file, but any process running as this user can, and it is captured by backups. - Saving a token now reports that it is restarting the agent while it waits. The write is instant, but the call does not resolve until the harness respawns — about 17 seconds — during which the row previously showed stale text behind a disabled button.
Removed
- The DMG is no longer signed and notarized separately from the app. Notarizing the app is what makes distribution work; the container rides along. Measured: an unsigned, unnotarized DMG holding a notarized and stapled app, carrying
com.apple.quarantineand opened through Finder, mounts with no block and the app inside assesses asaccepted, source=Notarized Developer ID.
Upgrading
MCP configuration migrates automatically from desktop.json into mcp.json on first launch, and the old token store is removed. Any MCP token stored by v0.1.0 is discarded and must be re-entered once. The two formats both look like {"id": "string"} — the old one holding base64 ciphertext, the new one a token — so they cannot be told apart by inspection. Reading a leftover ciphertext as a token would send it to a server as a bearer credential, so an unversioned document is discarded rather than guessed at.
Download: DeepSeek Harness-0.2.0-arm64.dmg — Apple silicon, signed, notarized, and stapled (the disk image as well as the app, so it verifies without a network round trip).
The bundled harness plugin @onetest/dsh-desktop-pane is unchanged at 0.2.1.
v0.1.0
First release of the DeepSeek Harness desktop shell for macOS (Apple Silicon).
It runs the DeepSeek Harness as a child process, discovers the port it binds, and loads its Web UI in a native window — with a menu-bar tray, a global show/hide shortcut, turn-completion notifications, and a dsh:// handler. It is a shell, not a fork: it never modifies the harness.
Install
Download the .dmg, open it, drag the app to Applications.
The app is signed with a Developer ID and notarized by Apple, so it opens with no warning and no trip through System Settings. The ticket is stapled, so it verifies offline.
Requirements
- macOS on Apple Silicon
- Node 22 or newer
- Either a local
deepseek-harnesscheckout with its frontend built (pnpm run build:web), or nothing at all — the Settings window can install the published@deepseek-ai/dshpackage for you
On first launch there is no configuration, so Settings opens instead of a harness. Point it at a checkout or choose a managed install, and save.
In this build
- Harness source — a local checkout, or a managed install pinned and cached under
$DSH_HOME/runtimes - Plugins — install npm packages into the harness, each with its own configuration, with update offers for unpinned entries
- MCP servers — connect the agent to remote MCP servers, with presets for Tavily and GitHub. Tokens go to the macOS Keychain, never to a config file, and the generated harness overlay refers to them by environment variable rather than by value
- Notifications and shortcuts — a turn-completion ping and a configurable global hotkey
Known limitations
- Apple Silicon only. No Intel, Windows, or Linux build is produced.
- MCP servers needing a browser sign-in cannot be added. Linear and Atlassian are listed but disabled — both accept only OAuth, which this app cannot do yet.
- A plugin that requires its own configuration can fail the whole harness boot, not just itself.
dsh://links only focus the app. The harness Web UI has no per-session URLs to deep-link to.