packages/dsh-task-board
zhu1090093659/dsh-web侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
免工作区自由对话,产物自动提升为独立项目工作区;dsh-scratchpad 的持续维护 fork,跟当前 DSH 保持可用。

Workspace-free free chat for DeepSeek Harness. Start a conversation without picking a project directory: a button beside the sidebar's new-session button opens a shared scratchpad workspace, every tool stays available, and sessions that produce real files are automatically promoted into isolated sibling project workspaces with a new-session handoff.
This is a maintained fork. It is based on
1264459640/dsh-scratchpad(MIT), which has had no commits since 2026-08-30 and no longer works on current DeepSeek Harness. The upstream copyright notice is preserved in LICENSE.
| Change | Why |
|---|---|
Starts sessions through uiWorkspace.startSession, not workspaces.startSession | startSession moved to the uiWorkspace service; upstream's call site no longer exists, so every click failed silently while the host quietly created blank sessions the sidebar never shows. Falls back to the old location on older harnesses, and reports the missing capability when neither exists. |
| Icons resolved by candidate name, with a bundled inline-SVG fallback | The primitives package renamed IconXxx16 to IconXxx<Weight><Size>; upstream's icon references resolved to undefined, React threw #130, and the crash took down the whole sidebar entry. |
| The entry sits beside the shell's new-session button | Two 50% controls in one row instead of a full-width section above the workspace list. Upstream deliberately chose the section layout; this fork prefers the compact one. If the shell button cannot be located, the entry falls back to the section, then to a footer button. |
| Two test-harness fixes | config-test pinned ~/.dsh (red for anyone running the suite inside a harness) and host-test used an uncanonicalized os.tmpdir() path (red on macOS). |
Everything else - the promotion pipeline, the commands, the configuration - is upstream's work, unchanged.
| Capability | How |
|---|---|
| One-click free chat | A 「自由对话」 button paired with the shell new-session button (56px rail collapse: a single sparkle icon button) calls GET /scratchpad/open, then uiWorkspace.startSession(workspaceId) opens a blank session bound to the shared scratchpad. |
| Shared default directory | All free chats share ${DSH_HOME:-~/.dsh}/scratchpad/ (mkdir + workspaceRegistry.create at startup, idempotent). |
| Fully open tools (no stripping) | The plugin never restricts tools - bash, write/edit, web search etc. all work; safety stays with DSH's own sandbox + approval. |
| Isolated artifact promotion | When a session produces files, its file-set is copied (never moved - the scratchpad is shared) into ${DSH_HOME:-~/.dsh}/projects/<date>-<slug> - a sibling of the scratchpad, never a child - registered as a new workspace, and a banner offers 「在新工作区打开」 (uiWorkspace.startSession(projectWorkspaceId)) so you continue in a fresh session bound to the project. |
| Detection | Two layers: fs/observed + fs/write-intent events (exact absolute paths, attributed via the acting agent's session) plus a per-turn recursive scratchpad scan that catches files bash/subprocess created (bash emits no fs events). A single global snapshot prevents two sessions from double-claiming a file. |
workspaceRegistry from
@deepseek-ai/dsh-workspace, webServer, commands, fs, timer,
sessions, sessionQuery, agents - all shipped in the web profile's
base/web-app bundles; no extra composition rows needed).zod and @deepseek-ai/schemastery (declared in package.json).uiWorkspace.startSession and falls
back to workspaces.startSession on harnesses that still expose the capability
there. When neither exists the click reports the missing capability instead of
silently doing nothing.@deepseek-ai/dsh-client-ui-workspace 0.1.7-rc.2
and 0.2.1-alpha.1, and @deepseek-ai/dsh-client-ui-primitives 0.1.7-rc.2.# from the npm registry
dsh plugin --profile web add -w dsh-scratchpad-plus
# from a source checkout (development)
dsh plugin --profile web add -w link:/abs/path/to/dsh-scratchpad-plus
Then restart dsh web or the DSH Desktop app. The 「自由对话」 button appears
next to the sidebar's 新会话 button; collapse the sidebar for a single icon button
between the new-session and workspace icons.
dsh plugin --profile web remove @banana-peeljj12/dsh-scratchpad
dsh plugin --profile web add -w dsh-scratchpad-plus
Install only one of the two: both register the same loader row id (scratchpad)
and the same /scratchpad route prefix. Your data is untouched - this fork uses
the same ${DSH_HOME:-~/.dsh}/scratchpad and projects directories, and existing
scratchpad workspaces keep working.
In the web profile's cordis.patch.yml (or by merging this package's
cordis.patch.yml):
- insert:
- id: scratchpad
name: 'dsh-scratchpad-plus'
config:
scratchpadPath: '' # default ${DSH_HOME:-~/.dsh}/scratchpad
projectsPath: '' # default ${DSH_HOME:-~/.dsh}/projects
autoPromote: true # false: only the manual command stays active
slugMaxLen: 8
Path rules enforced at load:
~ expands to the user home; $DSH_HOME (when set) replaces ~/.dsh.projectsPath must not equal or nest inside canonical
scratchpadPath - the plugin throws a clear config error otherwise (the
isolation invariant is the point).| Command | What it does |
|---|---|
/scratchpad | Prints the scratchpad and projects paths, the registered workspace id and the promotion count. |
/scratchpad-tidy | Lists promoted originals still left in the shared scratchpad that no other live scratchpad session references. |
/scratchpad-tidy --apply | Removes those originals. |
dsh --profile web --dump-config | grep scratchpad), then reload the window
(Cmd+R). A crashed entry logs slot entry crashed in 'sidebar.footer.action'
in the browser console - that is what an icon rename looks like.no startSession capability in the browser
console: the harness exposes neither uiWorkspace.startSession nor
workspaces.startSession. On a harness this fork has not seen yet, please open
an issue with the DSH version.A DSH session's cwd is fixed in its immutable SessionHeader, and workspace
membership is derived from that canonical cwd (attachSession validates
realpath(header.cwd) === workspace.path). There is no API to move a live
session to another workspace. Promotion therefore:
mkdir the projects root and atomically claim a unique <date>-<slug>[-N]
directory (mkdir({recursive:false}) + EEXIST retry);projects/<slug>/artifacts/), preserving each file's path relative to the
scratchpad root (so scratchpad/src/a.go lands at artifacts/src/a.go - no
basename flattening, no collisions between same-named files in different
subdirs); a half-copied project never reaches the registry (copy failure rolls
the whole claimed dir back);workspaceRegistry.create(projectDir, title) - only after the directory exists
(the registry realpaths the path);The scratchpad session stays put; originals remain in the shared scratchpad (copy
semantics) and can be cleaned with /scratchpad-tidy.
/scratchpad-tidy --apply, which itself
only lists files that no other live scratchpad session still references.npm ci
npm test # all suites
node test/open-session-test.mjs # client half: starters, icons, placement
The client half is a hand-written, zero-build browser bundle (lib/client.js,
window.__ModuleLoader__.load + react/jsx-runtime + dsh-client-ui-primitives)
registered through the public slot system - no build step. npm test is expected
to be green on Linux and macOS, with or without an ambient DSH_HOME.
Maintainer-side manual checks live in docs/manual-verification.md.
Reproducible render check (needs a running instance and Google Chrome):
DSH_HOME=~/.dsh node scripts/verify-render.mjs # dsh web on :3080
DSH_HOME="$HOME/Library/Application Support/dsh-desktop/harness" \
node scripts/verify-render.mjs --host 127.0.0.1:43129 # DSH Desktop
It navigates headlessly, asserts that the host node is injected directly after the shell's new-session button, that the two share a row with comparable widths, and that no slot entry crashed.
| Path | What it is |
|---|---|
src/ | Host half: config, paths, file-set tracking, promotion |
lib/client.js | Zero-build browser bundle (no compile step) |
test/ | Unit + integration suites (npm test) |
scripts/verify-render.mjs | Headless render check against a live instance |
docs/ | Maintainer checklist and the screenshot above |
display:none anchor inside
its sidebar.footer.action registration, resolves the sidebar root through an
explicit per-step criteria chain (closest('[data-slot="sidebar.footer.action"]')
-> footerActions -> footArea -> root), and inserts a flex:none host div directly
after the shell's new-session button. CSS :has(> .sp_pairHost) turns the sidebar
column into a two-column grid for exactly those two children - the React-owned
button is never reparented. createPortal renders into the host, so locales and
contexts keep working. Every failed criterion degrades visibly: paired ->
full-width section before the workspaces region -> footer button. The host carries
a data mark for idempotent reuse and an owner token so unmount/HMR cleanup never
leaves residue.sidebar.footer.action and
conversation.input.dock are additive list slots; the plugin registers nothing
into sidebar.workspaces/sidebar.settings (both single) and so cannot shadow
core UI or other plugins; unloads cleanly (every host registration is a fiber
effect).--dsw-alias-* variables; light/dark
follows the shell for free (color-scheme)./scratchpad-tidy --apply.Remove the plugin row (or dsh plugin --profile web remove dsh-scratchpad-plus) and
restart. All host registrations (routes, commands, listeners) are fiber effects and
disappear; the registered workspaces stay usable as ordinary workspaces, and no
physical files are deleted.
MIT. Forked from 1264459640/dsh-scratchpad;
see LICENSE for both copyright notices.