# dsh-task-board [中文](README.md) | English [![npm](https://img.shields.io/npm/v/@firetruck666/dsh-task-board)](https://www.npmjs.com/package/@firetruck666/dsh-task-board) [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Node](https://img.shields.io/badge/Node-%5E22.19.0%20%7C%7C%20%3E%3D24.0.0-339933)](README.en.md#requirements) A task-board plugin for the DeepSeek Harness web GUI. It adds a **Task Board** entry in the sidebar and manages work on a five-column kanban board, where each task is actually executed by a real DSH session and its status is written back to the card. The plugin does not modify DSH source, and removing it restores the interface. Board data lives on the DSH host process, so a desktop and a phone pointed at the same deployment see the same board, synchronised over SSE. Narrow screens switch to a compact layout. The Chinese [README.md](README.md) is the source of truth; this file mirrors it. ## Contents - [Requirements](#requirements) - [Install](#install) - [Updating](#updating) - [What it does](#what-it-does) - [Data locations](#data-locations) - [Building from source](#building-from-source) - [Contributing](#contributing) - [Troubleshooting](#troubleshooting) - [Naming](#naming) - [License](#license) ## Requirements - DeepSeek Harness `0.2.2-alpha.2` or later. This is the **minimum supported version** (the release the plugin is built and tested against), not "the current one": anything older is untested, and there is no upper bound — the plugin is updated alongside DSH, so just install the latest build after upgrading DSH. If loading fails, follow the Troubleshooting section. - For the web GUI (a `dsh web` you started yourself): Node.js `^22.19.0` or `>= 24.0.0`, and pnpm 10 or newer. - The **desktop app needs neither**: it ships its own Node and pnpm and runs package operations on its own runtime. ## Install One thing decides every command below: **the desktop app and the web GUI are two separate installs.** Each has its own profile (`~/.dsh/profiles/`). A plugin installed into one profile is visible to that surface and no other — installing it in the web GUI does not give you it in the desktop app, and the other way round. The two surfaces look identical and install the same way, but they install **separately**. | What you are using | Its profile | Who owns it | | --- | --- | --- | | The desktop app (DSH Desktop) | `desktop` | the app itself (it ships the runtime) | | `dsh web` (the GUI you started) | `web` | you, or whatever profile name you launched | So there is one rule: **install into the profile of the surface you are using.** Replace `` below with the word from that table. ### The desktop app **Use the app's own plugin page** — no terminal involved. Sidebar → Plugins → Add plugin → package name `@firetruck666/dsh-task-board` → pick an install source → Install. A terminal works too, but **only with the right `dsh`**: that profile is honoured by the application's own command line and refused by the one npm installs: ``` error: profile "desktop" is managed exclusively by the Electron application ``` To use a terminal, first have the app put its own `dsh` on the system: the menu's **Manage dsh Command…** (directly under Check for Updates…) does it in one step — on Windows into your user PATH, on macOS as `/usr/local/bin/dsh`. Then: ```sh dsh plugin --profile desktop add @firetruck666/dsh-task-board ``` Two things must be true first, or the command fails: the app has been **opened at least once** (it creates its own profile), and it is **fully quit** (while it runs, the files are locked). ### The web GUI ```sh dsh plugin --profile web add @firetruck666/dsh-task-board ``` ### Where the package comes from Either command above only says *which profile*; the package name says *what*. There are three forms. #### From npm ```sh dsh plugin --profile add @firetruck666/dsh-task-board ``` Installs the most recent release. This is the right choice for everyday use. #### From GitHub ```sh dsh plugin --profile add github:FiretrUCK666/dsh-task-board ``` Installs the current `main` branch. Like the npm install this is a ready-to-use package, not a development environment: it has no tests and no build tooling, so the code cannot be changed in place. Use it to pick up unreleased changes early; stability depends on the state of the branch at that moment. #### From a local checkout In the repository directory: ```sh pnpm install pnpm build dsh plugin --profile add . ``` This is the path for changing the code. See "Building from source" below. A local install links to your working directory rather than copying it, so one install is enough: after changing code, just run `pnpm build` and restart `dsh web` — do not re-run the install command above. Client-half changes need only a page refresh; host-half changes need the restart. See "A code change had no effect" under Troubleshooting. #### How the three differ The first two are installs: each delivers a ready-to-use package containing only the files in the publish list (runtime code, source, the bundle patch, documentation). The third is development: a full checkout you can edit and test. | | npm | GitHub | Local | | --- | --- | --- | --- | | Purpose | install and use | install and use | development | | You get | latest release | current `main` | your working tree | | Version | stable | newest, may be less stable | whatever you have | | Update source | npm version | the repository's default branch | not applicable | An npm install and a GitHub install contain essentially the same files; they differ in how new the code is. Install from GitHub to pick up unreleased changes, from npm for a stable release. ### After installing **Restart that surface**: stop the running `dsh web` and start it again, or fully quit and reopen the desktop app. Refreshing the page is not enough: the host half of the plugin loads inside the server process. The **Task Board** entry appears in the sidebar after the restart. The entry sits beside DSH's own Plugins panel. Selecting it puts the board in the centre stage; selecting the entry again — or any session in the sidebar — brings the conversation back. The plugin's switches live in two places, and they do different jobs. The toggle on the plugin's row under **sidebar → Plugins → Installed** is the **master switch**; the one at the top right of the plugin's own page (click the plugin name) does the same thing. **Off means the plugin is not loaded** (sidebar entry, board, overview, list and background routes all stop); **on restores it.** Under "Included components" on that page there are **four more rows, one switch each**, governing their own half (board / overview / list / AI interface) — see the FAQ. ### Try it once Two minutes after the restart is enough to see what this does: 1. Open **Task Board** in the sidebar, press **+ new item**, and write down one thing you actually have to do (a title is enough). 2. Open that row in the left rail, pick a workspace and an agent under the run configuration on the right, and press **Ask AI** — it becomes a card on the spot and a session starts working. 3. Back on the board: the card is in progress, and it lands in In Review by itself when the run finishes. The numbers along the top start reporting. 4. Open the card, read the result, press Pass. ### Nothing to configure by hand It works once installed — no config file to edit, no declaration to write. And **do not add anything to `cordis.patch.yml` yourself**: the plugin's row lives inside the package, and the install command only registers that package in the profile; the two together are the whole setup. Hand-adding a row makes the same plugin appear twice. | Step | Who does it | | --- | --- | | Register the plugin in the local profile | The install command (adds a package name to `dsh.profile.bundles`) | | The plugin's row (id, package name) | `cordis.patch.yml` inside the package, pointed at by `dsh.bundle.patch` in `package.json`; applies at composition | | UI entry, board stage | The plugin registers them into DSH's official extension points at startup | | Where data lives | `~/.dsh/storages/dsh_task_board/`, created on first run | | Display name, description, icon | `locale/` and `icon.svg` in the package, read directly by DSH | | Where that switch lives | The plugin manager writes `disabled` on this row (and only when you switch it off) | The one step you must take is **restarting that surface** (the host half lives in the server process and is not loaded otherwise). All three install routes — npm, GitHub, local — behave identically here, and so do the two surfaces. This plugin has no settings and needs no settings panel: the board's behavior (tasks, schedules, cruise, rules) is edited directly on the board, and on/off is the switch above. To remove it: ```sh dsh plugin --profile remove @firetruck666/dsh-task-board ``` A restart is required here too. Your task data is not deleted. ## Updating The board header's right toolbar has a permanent **Check for updates** button. One click answers three questions: **which copy is running** (which profile, where it lives, how it was installed), **whether there is a newer one**, and **how to get it**. It reads **the running copy's own location and manifest** rather than a name written into the code, so a local checkout on the web profile and an npm install inside the desktop app each describe themselves on the same machine. The latest version is read **by the host** as well: only it knows which registry this profile actually uses (official, a mirror, or an internal one), so a browser that cannot reach `registry.npmjs.org` no longer turns into "check failed". When it cannot read the latest version it says exactly that — it never reports "up to date" it cannot prove. | Installed via | What Update now does | | --- | --- | | npm | uninstalls, then installs the latest published version | | GitHub | uninstalls, then reinstalls at the current state of the repository's default branch | | a local checkout | `git pull --ff-only` in the repository, then rebuilds `lib/` with the package manager that repository declares | Uninstall-then-install is DSH's own update route (with HMR live it refuses to install over an installed package), so the plugin disappears for a few seconds. A restart may be needed afterwards, and **if the install fails the plugin stays uninstalled** — put it back the way you installed it. **Your task data is never touched** at any point: it lives in `~/.dsh/storages/dsh_task_board/`, not in the package. When the version has **not** changed, the dialog says exactly that instead of claiming an update — the run did not install the new version, which is not the same thing as failing. Two usual causes: the **installer's own release-age policy** (a package manager holds back a version published minutes ago — measured: an install run within the first ten minutes still lands on the previous one, so press it again once that window has passed), and the **version range this profile recorded**, which is what the **Declared as** line shows; uninstalling and installing again re-resolves it. Those three lines — which copy is running, where it lives, what it declares — answer "which one is this machine actually running" for good. The dialog also shows the equivalent terminal command (except on the desktop app, for the reason below), with your own profile name in it: ```sh dsh plugin --profile add @firetruck666/dsh-task-board@latest ``` ```sh dsh plugin --profile add github:FiretrUCK666/dsh-task-board ``` For a local checkout: ```sh git pull --ff-only && pnpm run build ``` **Desktop app**: that profile is the app's own, and the command above does not hold for it — the `dsh` npm installs refuses the `desktop` profile name, so that dialog offers no terminal line and points at the app's plugin page instead. Uninstall and re-add the package there to update. (The `dsh` the app's own menu installs is accepted; see Install.) With the plugin marketplace installed you can also press Update on its Installed tab. ## What it does ### One workbench, three pages The sidebar carries **Overview**, **Task board** and **Task list**, and the three share one navigation bar at the top — so which page you are on, and how much is waiting on each of the others, is visible at a glance. - **Overview**: four numbers (waiting on you, finished-but-unseen, due today, overdue), one pipeline row (how many cards in each column, and how long the oldest has sat still), the last seven days of output, and what happened most recently. Press a number to land on that page. - **They connect, and they also stay apart.** A card's "N items" chip jumps to the list filtered to that card; a row's card chip jumps back to the board and opens that card. Ignore those chips and the two pages go their own way. - **Each page can be switched off on its own**: one toggle per page in the plugin details, and a page that is off simply is not in the bar. ### The board Five columns, and reading them left to right is the whole life of a piece of work: Backlog / To do / In progress / In Review / Done. Drag a card between columns, or up and down inside one to set the order. - **Running is real.** Press "run" in the detail and a DSH session actually does the work, landing the card in In Review when it finishes. How many times it ran and whether the last one worked are on the card; a running card carries a yellow edge. - **Things waiting on you surface by themselves.** One line along the top: how many sessions have a question parked, how many cards need your verdict. Pressing it opens the notification centre, and the activity feed groups what happened by day. - **The model asks in the comment thread and you answer on the card.** A plan waiting for approval works the same way — approve it, or send it back to the composer, without a trip into the conversation. Each card's comments ARE its DSH session: queue or steer, and drag images and files straight in. - **Quote a line, then ask about it.** Select text in the conversation and a "Quote text" button appears beside it. Pressing it puts that text into the composer as a quote line, so you can carry on writing underneath it. What goes in is plain text, and it renders as a quote block in the thread — the same thing as quoting a line in a DSH conversation. - **The conversation folds one turn at a time: you say one thing, it answers with one thing.** The process in between (which tools ran, a moment of thinking, which context was injected) folds into one expandable row, leaving only the final answer once the run finishes. Open the row to read the detail. How much folds, when it folds, and which replies survive the fold all follow the three settings under "Settings · General" — this panel does not open a second set. - **Drag a session or a workspace in from the sidebar and you get a card bound to it.** - The review page is the full record of one run, with "pass" and "send back" right there. - Multi-select, then recolour, run or delete the whole selection in one go. Save the detail as a template; run configurations save as presets, one of them the default. ### The task list Some things are not ready to be worked on yet. Write them down first. - Press "+ new item": **the title comes first**, then body, steps and context notes. The left column is what you write, the right column is what you decide — priority, state, tags, the three dates, and which card it hangs off. - **Whether it hangs off a card is yours to choose**: no card (write it down, decide later), an existing card (nothing inside that card moves), or a new card — and for a new card you first see **what that card will run with** (workspace / agent / model / effort / permission) and it is born with what you saw. - A row's own state is **five steps**, in the board's own words and colours. With no card you pick freely; with a card it follows that card — but **your own "Done" outranks the card**: a finished thing does not turn back into unfinished work because the card is still running. That state chip is pressable: it takes you to the board and opens the card the row hangs off. - **"Ask AI" is the only press on this page that hands a row to a model, and it works on a row with no card**: if the card already has a conversation this is said into it, and if it has none this opens a run — **the card decides which**, so you never have to. What travels is the row itself (title, tags, body, steps, notes, and the images and files its body carries), not the card's run prompt, so a card whose own prompt is blank does not block it. - Everything that filters is in the left rail: the calendar, by date, by time untouched, priority, state, the agenda, and the deleted. Each row carries its number, 0 included. - Delete has an undo, and what you deleted sits under "deleted" for 30 days — recoverable, or erased for good. - Three independent times: start, target, deadline. Only a missed deadline turns a row red, and that row reads **Past due**; a slipped target reads **behind** — a broken promise and a moved plan are not the same thing. - Three details that are easy to miss: **the date boxes understand plain words** (`@tomorrow`, `10月15日`, `2026-10-15`, `+3`, `Wed`), and a spelling it cannot read is taught back to you rather than silently dropped; **the height of the body and notes boxes is yours** — grab anywhere along the bottom edge and it remembers; and on a phone the five columns become a horizontal track with nothing missing. ### Automation, several devices, and the AI doors - **Schedules** that fire on a timetable or chain onto completion. - **Rules** that send a session an instruction on the same triggers; the instruction can carry images and files, and the row says how many it holds. - **Auto-cruise**: one switch in the board header, with a cap on how many run at once and the hours they may run in. - Your phone opens the same data, and changes sync live. It works offline and catches up when the connection returns. - **Four slash commands.** `/task` hands your sentence to the current session's model, which decides whether it belongs on the board or the checklist. `/task-continue` lists what is unfinished on both and asks which one to start. `/task-status` reports the state and changes nothing. `/task-cleanup` tidies both: recoverable deletes happen, and anything irreversible is put to you first. - **The AI can read what it writes.** It can read a card down to its run configuration, schedule, session rules and attached sessions, so "add a rule to this card" no longer needs you to copy a session id off the screen. It reads the shape of the board by default and goes deeper only when it needs to. - **"Accepted" is not "done".** A receipt separates three things: really written, handed to the engine and not finished, or queued until the engine comes online. And one more it says outright: "not executed", because the browser holding the seat is running a build that cannot do that action. - **The session dots on a card open.** With something unread they open that session; otherwise they open the card's session list. Same on a phone. - Keyboard: `A` new, `J`/`K` move, `X` multi-select, `1`–`4` priority, `E` rename, `⌘Z` undo, `Esc` closes one layer. **What the words on a card mean.** The five columns are **Backlog / To do / In progress / In Review / Done**. They all read from **one derivation**, so the same card gives the same answer wherever you see it: | The word you see | What it is saying | When it goes away | | --- | --- | --- | | **Waiting for you** (on failure: also *paused*) | The card is in **In Review** and there is a result from that **conversation** you have not looked at | Open that conversation's thread, or press Pass / Send back | | **N waiting** | N conversations have a question parked for you (approval / plan confirm / a question) | **Answer on that card right in the board** — approve, ask for changes, or pick one of the options it offers; going into the conversation is optional | | **M in review** | M cards sit in **In Review** with a conversation you have not seen | Same as the first row | | **New** / **New comment** | This card has **new content** you have not opened | Opening the card clears it | | **N to handle** | N conversations are waiting on you (shown when N > 1) | Answering them one by one | | **N runs** | How many times this card has run — history, not a nudge | It never clears on its own | | **Failed · paused** | An automation stopped after its last failure | Re-enable it in the detail | **「Waiting for you」 and 「New」 are two different things.** *New* is 「this card you have not opened」; *waiting for you* is 「that conversation you have neither opened nor decided on」. **Cancelling is not 「waiting for you」** — cancellation is an abort, and it only ever shows in the thread and the activity feed. ## Data locations - Board source of truth: the `~/.dsh/storages/dsh_task_board/` directory. Under `documents/` there is one file per data kind: `board.json` holds the task ledger, cruise, schedule presets, run presets and deletion tombstones, and `items.json` holds the task list. Human-readable, written atomically; back the directory up directly, or delete it to clear both board and list. - Browser `localStorage` holds the offline mirror. Two of them CARRY DATA and both need backing up: `dsh.taskBoard.v1` (the board) and `dsh.taskBoard.items.v1` (the task list) — **backing up one and losing the other is the same as losing the list**. The rest (`cruise` / `presets` / `runPresets` / `templates` / `drafts` / `preSync`) is this browser's own state or a one-time backup, not a second truth. **The key names are whatever `src/client/` actually writes**; this lists what is worth your worry, not every key. - On the first connection, diverging local data is backed up to `dsh.taskBoard.preSync.v1` and the host wins. - Without a storage backend on the host, the board falls back to a pure `localStorage` mode. ## Building from source Requires Node.js 22 or 24, pnpm, and access to the public npm registry. Types and runtime APIs come from `@deepseek-ai/*`; no DSH source checkout is needed. ```sh git clone https://github.com/FiretrUCK666/dsh-task-board.git cd dsh-task-board pnpm install pnpm build # emits lib/index.js and lib/client.js pnpm typecheck pnpm test pnpm verify ``` `lib/` is a build artifact, but it is committed. Installing from GitHub or npm only copies files and never runs a build, so a repository without `lib/` would install a plugin that cannot start. Rebuild after changing source and commit `lib/` together with that change; CI checks that the two agree. `AGENTS.md` in the repository root records the project conventions, the architecture index and the release process; it is the reference AI assistants work from in this repository. ### Design system preview When you are working on the interface there is a page to compare against: set `DSH_TASK_BOARD_DESIGN_PREVIEW=1` before starting, and the sidebar grows a "Design system" row. It lays out the design variables, the type ladder, every button and chip variant, the five statuses, every icon, the data palette, the feedback grammar and the breakpoint table at once — and every specimen is **the real component itself**, so what you see there is what the panels render. ```sh DSH_TASK_BOARD_DESIGN_PREVIEW=1 pnpm dsh web # or add that variable to however you start it ``` It is registered only when that variable is set, so without it the page is **never registered** — it is not in the sidebar, it is not a switch on the plugin page, and an ordinary user never runs into it. (Its code ships in the same browser bundle as the two panels; what keeps it out of the interface is two gates: the host announces the surface only when that variable is set, and the browser registers nothing when it cannot read the announcement.) ## Contributing Issues and pull requests are welcome. Before you start, read [CONTRIBUTING.md](CONTRIBUTING.md): development setup, the pre-PR checklist and the hard rules. Load-failure reports should include three things: your DSH version, the plugin version and the error text. ## Troubleshooting **No sidebar entry after installing.** The host half loads in the server process. Restart `dsh web`; refreshing the page is not enough. **Where the entry and the switches are.** The board, the overview and the task list are three panel icons in the sidebar, beside DSH's own Plugins panel. The switches live on the plugin's own page: sidebar → Plugins → Installed → Task Board. There the five rows — board, overview, task list, AI surface and host half — each carry one switch; turning a row off unregisters that half. If you see no entry at all, this client is still serving an older front-end bundle — refresh the page. **The plugin fails to load after a DeepSeek Harness upgrade (the page says "Failed to load plugins").** DSH's internal interfaces change between releases, and the plugin has to follow. Two steps: 1. Update the plugin to the latest release, then restart `dsh web`: ```sh dsh plugin --profile web add @firetruck666/dsh-task-board@latest ``` 2. If it still fails, the plugin has not caught up with your DSH version yet. Please open an [issue](https://github.com/FiretrUCK666/dsh-task-board/issues) with three things: your DeepSeek Harness version, the plugin version (**on the plugin page's Installed list — not in Settings, this plugin has no settings**), and the exact error text from the page. Those three are enough to locate the cause. **The board opens, but devices do not sync and the task list stays empty.** That is what a host half which failed to load looks like, and it is the easiest failure to miss after a DSH upgrade: the board still works on the page, but its data stays in that one browser, so another device is not looking at the same board and schedules and cruise never actually fire. In this state the task list says "cannot reach the host copy right now, showing this device's copy" rather than pretending you have nothing. Fix it the same way as the entry above — update the plugin, then restart `dsh web`. **The board changed shape after opening the right sidebar.** That is the responsive layout working, not a fault. DSH's right sidebar takes about 45% of the viewport with a 300px floor, and the centre column keeps 400px, so opening it makes **the board's own box narrower**. The board responds to the width of its own box rather than the window, so once the box crosses the compact threshold the five columns become a horizontally scrolling track with an evenly divided tab strip. Closing the sidebar restores it. **A code change had no effect.** Changes under `src/index.ts` or `src/host/` need a `dsh web` restart; client-side changes only need a page refresh. Run `pnpm build` first in both cases. **Scheduled work runs twice with two devices open.** It should not: scheduling, cruise and follow-ups are arbitrated by a host lease. If the header shows a stale-server notice, open it to see the start time of the server process you are connected to; an old timestamp means a different, un-restarted instance is serving that address. **Moving to another machine.** Copy the `~/.dsh/storages/dsh_task_board/` directory. The browser `localStorage` is only a mirror. **No entry for the task list.** It is the **second row in the sidebar panel list, right under "Task Board"** — widen the sidebar and you will see two icons; click the second one to open the list, click it again to return to the conversation. With the sidebar collapsed to an icon rail the two sit side by side. If the row is **not there at all**: the page is probably still serving an older bundle, so refresh; if that does not help, check whether **Task List** is off on the plugin's own page — the plugin page lists this plugin's rows, and turning that one off does not affect the board. **The list disappeared when I opened the board panel.** That is not a fault; the main stage shows **one panel at a time** by design — they were never two things that fit on one screen. Click the list row in the sidebar to go back to it, or "back to conversation" to leave either panel. **`dsh plugin` cannot find pnpm.** Install pnpm (`npm install -g pnpm`) and re-run. ## Naming | Purpose | Value | | --- | --- | | Plugin id | `dsh-task-board` | | npm package | `@firetruck666/dsh-task-board` | | Permission preset route | `/api/dsh-task-board/permissions` | | Board data route | `/api/dsh-task-board/board` (`/items`, `/surfaces`, `/lease`, `/command`, `/events`) | | Other host routes | `/api/dsh-task-board/session-state`, `/update`, `/client-report`, `/attachments` (takes bytes, reads them back by ref) | | Host storage unit | `dsh_task_board` (one file per data kind under `documents/`) | | Board stage slot | `main` (`key: dsh-task-board`) | | Task list stage slot | `main` (`key: dsh-task-board-items`; same seat as the board, different key) | | Overview stage slot | `main` (`key: dsh-task-board-home`) | | Design preview stage slot | `main` (`key: dsh-task-board-design`; registered in development only) | | Sidebar entry slot | `sidebar.panellist` (`id` matches each panel's `main` key) | | `localStorage` keys | `dsh.taskBoard.v1` and friends | The plugin id and the package name are different things. The id names the loader row, the served browser asset, the routes, the storage unit and the extension points above; the package name is only what pnpm installed. A scoped package name never moves the id. The plugin has no settings of its own, so there is no settings route and no settings page: the switch in the plugin marketplace writes `disabled` on the profile row, and the plugin declares no setting fields. ## License MIT. See [LICENSE](LICENSE).