Bundle
@linsibin/dsh-task-board
DSH task-board plugin: adds a kanban tab (Chat/Trajectory/Tasks) to the DSH web GUI with drag-to-execute real agent runs.
- Source
- linsibin
- License
- MIT
- Updated
- Updated 2 days ago
Readme
# dsh-task-board
> A **task-board client plugin** for DeepSeek Harness Desktop. Adds a **「Tasks」** tab next to **Chat / Trajectory** in the conversation header, with a three-column kanban that can **truly execute** each task through the DSH agent engine.
[English](README.md) · [中文](README.zh.md)

---
## Highlights
- **Tab injection** — registers into the official `conversation.view` slot, sitting side by side with the built-in **Chat** (`chat`) and **Trajectory** (`trajectory`) tabs (same mechanism those tabs use).
- **Three-column kanban** — `Backlog` / `In Progress` / `Done` columns, grouped by an arbitrary **task group** name; group headers collapse/expand all cards underneath.
- **Rich task cards** — UUID `id`, `title`, `group`, Markdown `description`, `workDir`, `files[]` and `status`.
- **Drag-to-execute** — dropping a card into **In Progress** creates a **brand-new DSH session bound to the card's `workDir`**, sends the card description as a queued prompt, and the agent really does the work.
- **Live progress** — assistant output streams back onto the card in real time while the session runs.
- **Automatic lifecycle** — when the turn ends and the session idles, the card auto-flows to **Done** with the final assistant message as its result; failures/timeouts return it to **Backlog** with a visible error.
- **Re-run** — drag a Done card back into **In Progress** to execute it again.
- **Local persistence** — cards survive reloads (browser `localStorage`, key `dsh-task-board:v1`).
- **Theme-aware** — styled with DSH CSS variables (`--dsw-*`), auto light/dark.
---
## How it works (the 5-second version)
A DSH "client plugin" is a single npm package with **two halves**:
| Half | File | Role |
|---|---|---|
| Host half | `lib/index.js` | A Cordis plugin the Loader mounts in Node — a no-op for a pure-UI plugin |
| Browser half | `lib/client.js` | Registered into the Web GUI as `{ inject: [...], apply(ctx) }` |
`package.json` carries the two declarations the harness looks for:
```jsonc
"dsh": {
"client": { // browser-side declaration (platform MUST be "web")
"platform": "web",
"inject": [ /* client packages this module depends on in the boot graph */ ]
},
"bundle": { // profile-bundle declaration (provides a Loader patch)
"patch": "./cordis.patch.yml"
}
}
```
Inside `apply(ctx)`, the plugin registers a `conversation.view` entry:
```ts
ctx.slots.inject('conversation.view', () =>
ctx.slots.register({
name: 'conversation.view',
id: 'taskboard',
order: 20, // chat = 0, trajectory = 10, taskboard = 20
locale: 'taskboard',
label: () => t('view.task'),
}, TaskBoardView),
)
```
When a card is dropped into **In Progress**, the browser half runs the real execution pipeline:
1. `ctx.sessions.create({ cwd: task.workDir })` — the host accepts any absolute `cwd`; a new session is created **per run** (its `sessionId` is stored on the card so you can reopen and inspect it later).
2. `binding = ctx.sessions.binding(sessionId)` → `binding.session.open()`.
3. `session.prompt([{ type: 'text', text: taskDescription }], 'queue')`.
4. Subscribe to `binding.eventSource` and stream `assistant/live-chunk` text back onto the card.
5. Poll the session snapshot: when the window shows `user/message` → `turn/end`, `running === false`, the queue and pending submissions are empty → mark **completed** with the last `assistant/message` text.
6. Errors (`promptError`, `lastAgentError`) or an 8-hour timeout send the card back to **Backlog** with the error shown.
> Event-window note: session window entries are shaped `{ type: 'event' | 'transient', event: { type, seq, time, data } }` — the event kind lives on the **inner** `event` object.
---
## Install
The plugin targets the desktop profile that runs your GUI.
### Option A — DSH CLI (recommended)
Run inside a DSH terminal:
```sh
dsh plugin --profile desktop add <absolute/path/to/dsh-task-board>
```
Because the package declares `dsh.bundle`, the CLI appends it to `dsh.profile.bundles` in `~/.dsh/profiles/desktop/package.json` and installs it as a direct dependency.
### Option B — manual
1. Edit `~/.dsh/profiles/desktop/package.json`:
- add the package under `dependencies` (a `file:` spec pointing at the absolute path);
- add `"@linsibin/dsh-task-board"` to `dsh.profile.bundles`.
2. Run `pnpm install` inside the profile directory.
3. Restart DSH Desktop.
### Option C — npm
```sh
npm install @linsibin/dsh-task-board
```
Then add `"@linsibin/dsh-task-board"` to `dsh.profile.bundles` in your desktop profile and restart DSH Desktop.
> Never hand-edit `cordis.yml` — it is rewritten on every launch. Use `cordis.patch.yml` or the flows above.
### Enable
**Restart DSH Desktop** (bundle-list changes are never hot-applied). After restart, open or create a conversation: the header tab strip (which already shows **Chat / Trajectory**) will now also show **Tasks**.
> The tab only appears while a conversation is open — that is how the official `conversation.view` mechanism works, not a limitation of this plugin.
---
## Verify
| Acceptance item | How to check |
|---|---|
| Tasks tab appears | Open any conversation → tab strip shows **Tasks** after Chat / Trajectory |
| Create a card with group & files | **+ New Task** → fill title / group / description / workdir / files (one per line) → card appears in Backlog, grouped |
| Drag triggers a real run | Drag card into **In Progress** → running indicator + streaming output; console logs `[dsh-task-board]`; a new session shows up in the session list |
| Auto-flow on completion | Turn ends & session idles → card moves to **Done** with a result summary |
| Re-run | Drag a Done card back into **In Progress** |
| Group collapse | Click a group header to collapse/expand its cards |
| Persistence | Reload the page — tasks are still there |
**Failure path**: a rejected prompt, agent error, or 8 h timeout returns the card to **Backlog** with a ⚠ message; drag it in again to retry.
---
## Development
```
node scripts/build.mjs # rebuild lib/client.js from src/client.tsx
node scripts/build.mjs --watch # rebuild on changes (for client HMR)
```
- **Offline build** — the script uses the TypeScript that ships inside the DSH profile (`~/.dsh/profiles/node_modules/typescript`) via `transpileModule` (syntax transpilation only, no type-checking, no network). Override with the `DSH_TS_LIB` env var if needed.
- **Bundle purity** — at runtime the browser bundle may only `require()` platform seed modules (React, `@deepseek-ai/cordis`, …). This plugin therefore ships **zero third-party runtime dependencies**; type annotations in the source are for readers only.
- **HMR** — after the first install, editing `src/client.tsx` and rebuilding rewrites `lib/client.js`; the always-on client HMR reloads that plugin in place (plugin React state is lost, sessions/runtime survive). Manifest changes still need a restart.
### Project layout
```
dsh-task-board/
├─ package.json # dsh.client (web) + dsh.bundle (patch) declarations
├─ cordis.patch.yml # bundle patch: inserts this plugin as a Loader row
├─ tsconfig.json # editor/type-reading only; build does not use it
├─ lib/
│ ├─ index.js # host half (no-op Cordis plugin for the Loader)
│ └─ client.js # browser half (BUILT artifact — edit src/, then rebuild)
├─ src/
│ └─ client.tsx # browser-half source: kanban UI, drag & drop, runner
└─ scripts/
└─ build.mjs # TSX → CJS + ModuleLoader registration shell → lib/client.js
```
### Notable details
- Because the `file:` dependency install is a **copy**, after changing `src/client.tsx` + rebuilding you must refresh the profile copy (`pnpm install` may say “Already up to date” — copy `lib/client.js` over manually, or use `pnpm link` for a live dev loop).
- Running against an arbitrary `workDir`: `session.create({ cwd })` accepts any absolute path, but whether the host fs/sandbox permits an unregistered directory depends on host configuration — register the directory as a workspace first if it gets blocked.
- Every execution creates a **new** session in the GUI session list; open it anytime to review exactly what the agent did.
---
## Compatibility
Built against DSH Desktop 2.0.5 internals (`@deepseek-ai/dsh-*` 0.1.2-rc.1) and verified on a live desktop profile. Internal APIs may change across DSH versions.
## License
MIT
Install
dsh plugin --profile web add github:linsibin/dsh-task-board#bbd45b6947faed2324c70d5ea089aefbb6b4900f
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install linsibin-dsh-task-board from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.