Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:DamonKoy/dsh-web-ui#path:/packages/dsh-task-board
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
A hot-pluggable DeepSeek Harness (DSH) client GUI plugin: it adds a task board entry below "新会话" (New session) in the sidebar; clicking it switches the middle column entirely to a multi-column kanban view. Tasks execute for real through DSH's own session mechanism (session.prompt), and execution status is written back to the card in real time.
- No DSH source modification: mounted as a cordis plugin + browser DOM extension (add-on shape identical to
dsh-web-ui/packages/skins/skin-center). - Unmounting restores the original state; other managed segments (dsh-skin / skin-center / personal config) are unaffected.
- Task data persists locally; it survives a page refresh and a DSH restart.
Features
- Sidebar entry: injects a "任务看板" (task board) entry row inside the sidebar column (
[data-pane="sidebar"]on older shells,[class*="sidebarCol"]on the DSH 0.1.0-rc.6 AppFrame layout) below the new-session button (wide rail shows icon + text, collapsed rail shows a bare icon, adapting to DSH skin tokens). - Multi-column board: five columns — 待规划 (to plan) / 待办 (to do) / 进行中 (in progress) / 已完成 (done) / 已失败 (failed); cards show title, description, status, update time, and execution count; the top supports search filter, new task, and back to chat.
- Task details: click a card to open details (title/description/execution prompt/execution log) — it does not execute on a single click; the details offer "执行 / 重新执行" (Run / Re-run), "删除" (Delete, with confirm), "查看会话" (View session, jumps to the execution transcript), and a manual move to 待规划/待办.
- Real execution: on "执行" (Run), the plugin connects a workspace session through the client runtime (
workspaces.connectWorkspace, reusing a blank session or letting the host create one), names the session after the task title, and drives a real agent viasession.prompt([{ type: 'text', text }], 'queue'); it then subscribes to that session's snapshot and, once the round really finishes, sets the card to 已完成/已失败 and records the execution result. The execution session appears in the session list and can be opened to view the real transcript. - Status write-back: card status (进行中 → 完成/失败) is driven by the real session state; after a page refresh/restart, leftover running tasks are auto-reconciled against the current session state (reconcile).
- Scheduled tasks: the details panel can schedule a task — an enable switch + a 5-field cron expression (分 时 日 月 周, supporting
*/*/n/a-b/ comma lists) + common presets (daily 09:00, every hour, every 10 minutes, Mondays 09:00); enabling computes and persists the "下次运行时间" (next run time), and the card shows a scheduled marker; at the due time it automatically takes the same real-execution path (as manual run), and the execution session remains linkable. - System-prompt injection: the host half (
src/index.ts) registers aplugin:task-boardsection (order 200) viaSystemPrompt.section, declaring this plugin's existence, capabilities, and limits to every agent — it is injected when the plugin is in the composition (after mount + DSH restart) and disappears when removed (after unmount + restart), so an agent needs no external docs to know how to work with this board.
Directory structure
package.json / tsconfig.json / tsdown.config.ts # standalone repo build
build/tsdown.client.ts + build/web/src/platform.ts # client bundle preset copied from the DSH checkout (kept in sync with the running version)
src/index.ts / src/invariant.ts # host half: only injects SystemPrompt section (no other behavior)
src/client/index.ts # apply(ctx): wires runtime services + mounts DOM
src/client/sidebar-entry.ts # sidebar entry injection (self-healing MutationObserver)
src/client/board-mount.tsx # middle-column board mount + show/hide toggle
src/client/board/*.tsx # React board views (columns/cards/details/new/confirm)
src/client/board.module.css # styles (--dsw-* tokens, adapting to theme/skin)
src/core/tasks.ts # task model + state machine (pure functions)
src/core/schedule.ts # cron parsing + next-run time (pure functions)
src/core/scheduler.ts # browser scheduler (ticks every minute to fire due tasks)
src/core/store.ts # persistence (TaskStore interface + localStorage impl)
src/core/execution.ts # real execution service (session connect/prompt/settlement watch)
src/core/controller.ts # controller (ledger state, view state, navigation awareness)
tests/*.spec.ts # automated tests: storage/state transitions/execution trigger/cron/scheduling
scripts/dsh-task-board.js # one-click mount/unmount/status CLI
Why it is wired this way (research conclusions)
- No usable add-on slot in the sidebar: the sidebar shell only declares two single slots,
sidebar.workspaces/sidebar.settings, both already taken by ui-workspace / ui-settings; an external plugin cannot register a new slot (declaring means claiming, and duplicating throws). So the entry goes through the skin-precedent DOM injection, self-healed with a MutationObserver (when a React re-render touches the node it re-inserts within the same frame, no flicker). - The middle column cannot be replaced through a slot: the
conversationslot is single and already taken by ui-conversation. The board view mounts on the center column ([data-pane="conversation"]on older shells,[class*="centerCol"]on the DSH 0.1.0-rc.6 AppFrame layout) as a tail child node (outside React's ownership), toggled via the<html data-dsh-taskboard-active>attribute, keeping the chat subtree below mounted and stateful. - Persistence uses browser localStorage: client plugins run in the browser and DSH has no browser-writable file channel (matching skin-center's research on
cordis.patch.yml); localStorage is also how DSH's own client snapshot store (createSnapshotStorepersist) persists. - Execution rides the client runtime:
ctx.sessions.listsubscribes to session state (running/byId),ctx.workspaces.connectWorkspace()creates/reuses a session,session.prompt()drives a real agent, andctx.sessions.open()jumps to the transcript. - Background settlement relies on list reconciliation: an unopened session has no chat-snapshot window (cold), so settlement keys off the session list — every list change reconciles running tasks; result judgment takes, in order, "missing from list → cancelled / still running → wait / chat snapshot visible → by lastAgentError / tail of raw history → a turn-error node proves failure / otherwise success", and reconciliation is idempotent.
- Scheduled tasks run in the browser scheduler: the plugin is pure-client (no server channel), so "run at the due time" is done by the in-tab scheduler — a tick every minute, with an immediate catch-up tick when the page returns from the background; before firing it first moves "next run" forward to the next cron match so the same tick never fires twice; it does not fire early in page load (before the session-list baseline is ready), avoiding mis-execution. Limitation: the tab must stay open (schedules missed while closed are "miss = skip", and only already-deferred due tasks are caught up on the next open); a task that is 进行中 (in progress) skips the current due time and waits for the next cron match.
- Same-origin tabs share one ledger: additions, edits, and deletions in any tab propagate to the others through storage events (
LocalStorageTaskStore.subscribeExternal), so a task deleted in one tab can never keep firing from another tab's stale in-memory copy — nor be written back by that copy's later persistence (schedule roll-forward, execution settlement).
Install
Install the family aggregate package @linxin666/dsh-web-ui-all (all plugins and skins in one) or this plugin alone:
### 从 npm 安装(推荐)
dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
### 从仓库安装(开发调试)
git clone https://github.com/zhu1090093659/dsh-web-ui.git
cd dsh-web-ui
pnpm install && pnpm -r build
dsh plugin --profile web add link:$(pwd)/packages/dsh-task-board
After installing, restart dsh web — a "任务看板" (task board) entry appears below "新会话" (New session) in the sidebar; a page refresh is not enough, the process must restart.
Build
Prerequisites: Node ≥ 20 with the official NPM SDK reachable (configure the NPM_TOKEN env var + project .npmrc if still using private-scope auth; see the repo docs/plugins.md). Types and runtime APIs all come from the official NPM SDK (@deepseek-ai/* devDependencies); no DSH source checkout is required.
cd ~/code/dsh-web-ui/packages/dsh-task-board
pnpm install # first time (run pnpm install at the workspace root)
pnpm run build # produces lib/index.js + lib/client.js (tsdown + shared/tsdown.client.ts preset)
pnpm run typecheck # type check (SDK package types from node_modules)
pnpm test # vitest: storage read/write / state transitions / execution trigger
Mount / Unmount
This plugin uses the official profile-bundle shape (package.json declares dsh.bundle.patch + dsh.client, see cordis.patch.yml). Mounting = registering the dependency and bundle rows in the web profile manifest (~/.dsh/profiles/web/package.json) and installing:
# Mount (registers dependencies + dsh.profile.bundles, pnpm install; takes effect after restarting the GUI)
node scripts/dsh-task-board.js mount
# View status
node scripts/dsh-task-board.js status
# Unmount (removes the registered rows; restores the original GUI after restart; task data is kept)
node scripts/dsh-task-board.js unmount
The rows registered in the profile manifest:
{
"dependencies": { "@linxin666/dsh-client-ui-task-board": "link:/Users/zcl/code/dsh-web-ui/packages/dsh-task-board" },
"dsh": { "profile": { "bundles": [ "...", "@linxin666/dsh-client-ui-task-board" ] } }
}
Note: the profile layer (bundle rows,
dsh.clientmetadata) is read when the dsh web process starts, so a restart of the dsh web GUI is required after mount/unmount (a page refresh is not enough).
Data storage location
- The task ledger lives in browser localStorage under the key
dsh.taskBoard.v1(originhttp://127.0.0.1:<dsh web port>; same origin persists across refresh/restart). - Data is retained after unmount; to clear it, run
localStorage.removeItem("dsh.taskBoard.v1")in the browser console. - The storage layer is the
TaskStoreinterface (src/core/store.ts); it can later be swapped for IndexedDB or a host file channel without touching the upper logic.
Manual verification steps
npm run build→node scripts/dsh-task-board.js mount→ refreshhttp://127.0.0.1:3080.- A "任务看板" (task board) entry row appears below "新会话" in the sidebar; click it → the middle column switches to the five-column board.
- "+ 新建任务" (New task) with title/description/Prompt → the card appears in 待办 (to do).
- Click the card → details show content and Prompt; click "执行" (Run) → the card becomes 进行中 (in progress) (a session named after the task title appears in the session list); after the agent finishes the card lands in 已完成 (done) or 已失败 (failed), the detail execution log has a result and time, and "查看会话" (View session) jumps to the real transcript.
- Scheduled task: details → tick "定时运行" (Scheduled run) to enable, pick the preset "每 10 分钟" (every 10 minutes, cron
*/10 * * * *); a scheduled marker appears on the card; wait for the next whole 10-minute mark, watch the card automatically enter 进行中 (in progress) and eventually complete, with "上次触发" (last trigger) showing a time and a new execution-log row (the session is linkable). - Refresh the page / restart DSH → tasks remain; unmount the plugin → the GUI restores to its original state.
Acceptance checklist
- After mount, a "任务看板" (task board) entry appears in the sidebar; clicking toggles the board, and clicking a session item returns to the chat view
- New task (title + description/Prompt); tasks remain after refresh/restart (localStorage persistence)
- Click a card to open details (content + execution log); the details have "执行" (Run) and "删除" (Delete) buttons
- Execution really starts a session (its transcript is visible in the session list); card status follows the real execution progress; the details can jump to the execution session
- Delete has a confirm step, and the local store is synced-removed after deletion
- Scheduled tasks: cron config/preset/validation, next-run time, auto real execution at the due time, status write-back, scheduled card marker, scheduling resumes after refresh (browser-side scheduling, the tab must stay open)
- One-click mount/unmount; after unmount the GUI restores and other managed segments are unaffected
- README + automated tests covering storage read/write, state transitions, execution trigger, cron parsing, and the scheduler
Links
More in this category
zhu1090093659/dsh-web-ui#packages/dsh-web-ui-all★ 3649
Plugin and skin collection for the DSH Web UI: task board, Git graph, right-side panel, remote mobile UI, pet, live token stats, and a skin center.
omdsh-dev/DSH-better-sidebar★ 1731
Full sidebar workbench with file rendering and editing, terminal, Git, and subagents; third-party plugins can register new tabs.
ccch1mneyyy/dsh-TUI★ 1646
Claude Code-style full-screen terminal UI: pixel-whale header, live status line, and streaming thought expansion.
omdsh-dev/dsh-at-file★ 288
Codex-style `@file` mentions: search workspace files in the composer and attach their contents to prompts.
huiliyi37/dsh-tianshu-tui★ 194
A terminal UI (TUI) for DeepSeek Harness.
Nagi-ovo/dsh-visualize★ 161
In-conversation generative UI: the model renders interactive HTML cards into the chat stream, with streaming preview and sandboxed rendering.