The DeepSeek Harness Desktop application is an Electron shell around the same dsh runtime the CLI
launches. apps/desktop and apps/desktop-host are private: true and never reach npm, so this
page was written from the monorepo at tag dsh-v0.1.7-rc.2, which is a later release than the one
HARNESS_REFERENCE names. Where a claim below depends on a version, it says so.
The short answer: third-party plugins can be installed into Desktop, they are installed in exactly
the shape this tool analyses, and Desktop composes them through the same loader, the same bundles
and the same patch layers as the CLI’s web profile. Nothing here asks for a Desktop-specific
mode. What Desktop adds is one composition step outside the loader and — at 0.1.7-rc.2 — two
waterfall events the CLI’s 0.1.5-rc.3 catalogue does not declare.
web profileapps/desktop/src/project-manager.ts takes its bundle list straight from the shipped template:
const WEB_PROFILE = PROFILE_TEMPLATES.web as ProfileTemplate // project-manager.ts:32
export function createPluginProfile(projectDir: string): void {
initProfile(projectDir, WEB_PROFILE.bundles) // project-manager.ts:175
}
PROFILE_TEMPLATES.web is ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app']
(packages/boot/app-boot/src/profile.ts:183 to :184). There is no desktop entry in
PROFILE_TEMPLATES and no packages/bundle/desktop. apps/desktop-host/src/index.ts:25 to :29
calls the CLI’s own runProfile with profile: 'desktop' and patchFiles: [], and passes
resolvedProfile, which bypasses named-profile initialization — so 'desktop' is a name carried
into ProfileContext, not a template lookup. The layer stack it produces is the shared one
documented at apps/cli/src/profile-boot.ts:185 to :190: each bundle in dsh.profile.bundles
order, then the profile’s own cordis.patch.yml, then $DSH_HOME/cordis.patch.yml, then --patch
overlays (none, for Desktop), then the telemetry switch.
So Desktop’s rows are the core rows this tool already knows, for whatever harness version the
installed runtime is. CORE_ROWS needs no Desktop-specific entries.
A Desktop profile is a pnpm package directory. initProfile writes its package.json, an empty
cordis.patch.yml, and a pnpm-workspace.yaml pinning nodeLinker: hoisted — which the harness’s
own comment (profile.ts above PROFILE_PNPM_WORKSPACE) says exists so out-of-tree plugins share
the installation’s single Cordis instance. apps/desktop-host/src/index.ts:31 to :40 hands
runProfile a packageManager whose command is Electron’s own executable under
ELECTRON_RUN_AS_NODE, so package operations run inside the application.
Installing a package that declares dsh.bundle.patch activates it with no separate step:
reconcileProfilePlugins appends every new bundle dependency to dsh.profile.bundles
(packages/boot/app-boot/src/profile-plugins.ts:115, under the JSDoc line “New bundle dependencies
activate automatically” at :97). Appended means after @deepseek-ai/dsh-base and
@deepseek-ai/dsh-web-app, so a third-party layer patches over every core row those two inserted.
That is the exact act Tier A grades.
Desktop also ships a recovery path for when that goes wrong.
DesktopProjectManager.disableAllPlugins calls sanitizeProfile, which renames the profile’s
cordis.patch.yml aside and rewrites dsh.profile.bundles back to [base, web-app]
(apps/desktop/src/project-manager.ts:77, packages/boot/app-boot/src/profile-sanitize.ts:18). It
preserves installed packages and it does not touch $DSH_HOME/cordis.patch.yml, which
composeProfile applies to every profile and ranks above the per-profile layer.
apps/desktop-host/src/index.ts mounts four things on the booted context directly, after
runProfile resolves:
| What | Where | Reaches |
|---|---|---|
desktop-office |
index.ts:95, defined in office.ts |
mounts @deepseek-ai/dsh-tool-workspace-dependencies and @deepseek-ai/dsh-skill-office |
| update admission | installDesktopUpdateTaskControl, update-tasks.ts:34 |
a connection/request listener that answers 503 while an update holds the admission lock (:36) |
| quit inspection | installDesktopQuitInspection, quit-inspection.ts |
dispatches workspace/session-activity per live agent (:35) |
| platform session publisher | platform-session.ts:11 |
ctx.inject(['deepseekAccount']), forwards account sessions over private IPC |
None of these is a loader row. They carry no row id, so no patch entry can name them: - id:
desktop-office / disabled: true in a third-party layer is inert. Worth noticing that the same two
packages are rows under ids workspace-dependencies and skill-office in the sdk-app bundle
(packages/bundle/sdk-app/cordis.patch.yml:30 and :36) — the same code is a targetable row in one
profile and unreachable-by-id in another. None of the four provides a capability seam.
At 0.1.7-rc.2 the api-catalog declares 17 waterfall events against the 14 in WATERFALL_EVENTS.
Two of the three additions are the ones Desktop uses, and both are decision waterfalls by this
tool’s own rule — a short-circuit replaces what the harness would otherwise have done:
connection/request — “Admit or wrap an authenticated shared API request, including body
transfer… next: Delegate to the next listener or the shared API bridge”
(packages/extensions/tool-cordis/src/api-catalog.ts:3881, declared at :3877). A listener that
returns without calling next() owns the response: every authenticated call the Desktop renderer
or the Web client makes is answered by that listener instead of by the shared API bridge. That is
the tools/execute shape, already in DECISION_EVENTS because vetoing it substitutes the
built-in body.workspace/session-activity — “A listener prepends its own SessionActivity entries to the
result of next(); the registry’s innermost callback returns an empty list, so a composition
without providers archives freely. Any non-empty result refuses the archive without a write”
(api-catalog.ts:4321, declared at :4317). The refusal lives in the listeners and the inner
callback is permissive — the same structure approval/request has, and the reason that event is
graded critical. A listener that returns an empty list without calling next() suppresses
every other provider’s report, and a session with running agents, queued messages, live jobs or
armed reminders is archived — and, with stopActivity, stopped — as though nothing were running.The third addition, compaction/summary-error, is an error-recovery chain (api-catalog.ts:3873,
declared at :3869) and is not a decision waterfall.
None of the three is in WATERFALL_EVENTS yet, because this tool tracks latest and they
arrive in next. src/knowledge.ts explains that choice at HARNESS_REFERENCE. Until 0.1.7
becomes latest, B14 says nothing about a listener on any of them: this page is the warning, and
pnpm run sync -- --release 0.1.7-rc.2 prints the gap on demand.
workspace/session-activity and connection/request exist under other names at
0.1.5-rc.3. The catalogue at that version declares neither. Whether the underlying dispatch
predates the catalogue entry was not checked.0.1.7-rc.2. apps/desktop/package.json and
apps/desktop-host/package.json both carry the monorepo version, and the package set is pinned to
it (apps/desktop/src/core-package-set.ts, parseDesktopCorePackageSet’s
expectedReleaseVersion check) — but only for the checkout that was read. A shipped installer’s
own version was not observed.plugin-manager, tool-plugin-manager and ui-plugin-manager rows. They exist at
0.1.7-rc.2 and not at 0.1.5-rc.3, so the install flow they drive was not analysed against the
tracked release.