dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
为 dsh 提供无项目聊天:不选工作区创建的会话会获得自己的目录(~/Documents/DSH/<日期>/chat-NN),重新打开会话时复用,并留在「未分组」而不会变成侧栏项目;新建会话的 Hero 上新增悬浮清除徽标与「不在工作区中工作」一行,另有一个仅存在于浏览器端的哨兵工作区,让该 Hero 及其输入框保持可用。

Codex-style project-less chats for the DeepSeek Harness.
Click "new session" without picking a project and you just start typing. The plugin gives that chat its own private working directory, so the agent's file tools and shell commands run there — and when you come back to the chat later it keeps working in the same place. Nothing is added to the sidebar's project list.
DSH resolves a session's working directory in exactly one place:
// @deepseek-ai/dsh-api-session-controller
const cwd = workspace?.path ?? request.cwd ?? this.defaultCwd
this.defaultCwd is the Host process's own process.cwd(). The GUI always
sends a workspaceId, so that fallback is normally dead — but there was no way
to not pick a project, and any other caller creating a chat without one would
silently inherit the directory DSH was launched from.
This plugin takes over that one fallback and nothing else.
| Directory | ~/Documents/DSH/<YYYY-MM-DD>/chat-NN/ (%USERPROFILE%\Documents\DSH on Windows) |
| Grouping | Sessions stay out of the project list; DSH groups them under its native "Ungrouped" bucket |
| Resume | The directory lives in the session's own cwd header, so reopening a chat reuses it; files persist |
| Inheritance | An explicit project wins. "New session" in a project stays in that project; in a project-less chat it stays project-less |
| Entry | Hovering the blank-chat hero's project chip swaps its folder glyph for a circular clear badge — click it to leave the project. The picker menu also offers Work without a project. With no project selected the chip reads Choose workspace and no badge appears |
| New-chat screen | A project-less chat keeps DSH's own "not started yet" Hero — fish, headline, chip row, agent-preset control — because a client-only sentinel Workspace gives the composer its title |
| Cleanup | Nothing is deleted. A directory is only reclaimed when its session creation failed before anything could be written |
The package is a DSH bundle: package.json declares dsh.bundle.patch, so
installing it appends the bundle to the profile's dsh.profile.bundles and
activates its row.
For the web app, install it with:
dsh plugin --profile web add github:Castor6/dsh-auto-workspace
The Desktop app does not support installation through the command line. Open
Plugins from the left navigation bar and enter
github:Castor6/dsh-auto-workspace or the repository URL
https://github.com/Castor6/dsh-auto-workspace
to install it.
Set on the plugin's loader row in the profile patch
(~/.dsh/profiles/<profile>/cordis.patch.yml):
- id: auto-workspace
config:
root: '/Volumes/Work/DSH' # default: <home>/Documents/DSH
directoryPrefix: 'chat' # default: chat → chat-01, chat-02, ...
enabled: true # default: true
<root>/.dsh-auto-workspace.json maps session ids to directories. It is a
cache: delete it and the plugin rebuilds it from each session's persisted
header. Deleting the whole root removes every project-less chat's files.
Host half (lib/index.js, Node builtins only — a link:-installed plugin
loads through its real path, where no node_modules exists):
ctx.sessionController.create. Requests naming a workspaceId or an
explicit cwd pass through untouched; a request naming neither gets a
directory from the allocator. mkdir without recursive is the atomic
reservation, so concurrent creates cannot collide.SessionHeader.cwd is immutable once written, so a directory is named once
and never renamed — that is why the day folder plus chat-NN is fixed at
creation.resumeObserved reads
header.cwd and rejects a session that has none, so injecting a directory at
creation is what makes project-less chats durable.Browser half (lib/client.js, hand-written lazy-CJS bundle — no build step;
react, react-dom and @deepseek-ai/dsh-client-ui-primitives are
shell-provided seed modules):
conversation.hero.workspace at priority: -1 to shadow the shipped
picker for that single seat (the default priority is 0). The project list,
selection check and folder-error dialog are preserved.div[data-slot="conversation.hero.workspace"] with display: contents —
hides the glyph, and overlays a badge centred on its measured rect, drawn
smaller than the glyph box because a filled circle reads heavier than the line
art it replaces. Nothing keys off a label, so it survives a language switch.
The picker's footer gains a Work without a project row beside Add folder.
Neither affordance exists while no project is selected; a chip that cannot be
located at all leaves an inline badge instead.ctx.uiWorkspace.pickDirectory() — exactly what the
shipped …directoryFlow occupant resolves to. It cannot be delegated to that
hole: a slot occupant may only render child slots it declared itself, and a
child slot name may be declared once, so a shadowing occupant can never
render a hole the shipped registration declared.ConversationMainPanel disables it while chipTitle === undefined, and
chipTitle is only ever a Workspace title. Putting the session under a real
Workspace is exactly the sidebar project this plugin must not create, and it
would also move the chat out of Ungrouped. So the browser projection appends
one sentinel Workspace — client-only, never registered on the Host,
carrying no Sessions — and the Hero's own picker callback selects it, which is
what gives chipTitle a value. It is projected only while the main Session
is project-less, so navigating to a project drops it and the panel clears the
stale selection by itself.
That sentinel necessarily reaches the sidebar's group list, which renders a
row for every Workspace including empty ones; that single row is hidden by a
mutation-observed display: none.blank: false, awaitingFirstTurn: false) through
its per-session shell snapshot, which renders the ordinary active composer.
The chat stays usable; only the Hero is lost. Only that snapshot is touched —
the session list keeps the real blankness, so sidebar visibility, the native
blank-session reuse and this plugin's own reuse keep shipped behaviour.ctx.uiWorkspace.startSession so a project-less current session stays
project-less instead of falling back to the most recently active project.if (group.workspaceId !== void 0), and that
bucket has no workspace id, so it is inert. There is no slot inside a group
header, and replacing sidebar.workspaces would mean reimplementing the whole
browser; instead a capture-phase listener matches the button by the
accessible name the same locale service produces. A renamed label or a
restructured header stops matching and native behaviour returns.sessionController.create, uiWorkspace.startSession, sessions.binding's
snapshot store) rather than a published extension point. All of them are
defensive: if the shape changes the plugin logs a warning and stays inert
instead of breaking the app.chat-NN numbering is per day and best-effort: removing a day folder makes the
numbers reusable, so numbering is not monotonic over time.groupSection class-name suffix. Should a DSH
redesign break either, one empty project row titled Choose workspace becomes
visible — cosmetic only. Flipping HERO_FOR_PROJECT_LESS to false at the top
of lib/client.js removes the sentinel entirely and returns to the composer
override; the client bundle is re-read on every page load, so a refresh
applies it.