Back to home@Mason-1011

dsh-task-center

Task lifecycle plugin suite for DeepSeek Harness (dsh): agents claim work across sessions, wake up on schedule, quota-aware; idle/imported chats auto-mined into board candidates; full web kanban. | dsh 任务全生命周期插件族:跨会话认领、定时唤醒、额度感知、闲置会话自动抽取候选,配 Web 看板。

Stars
0
Language
TypeScript
Created
Aug 17, 2026
Updated
Aug 20, 2026
GitHub repo

Introduction

dsh-task-center

English | 简体中文

A personal task command center for DeepSeek Harness (dsh) — a full task-lifecycle plugin suite. You steer a long-lived backlog; agents claim tasks across sessions, wake themselves up on schedule to keep working, and progress stays visible to you at all times.

The problem it solves

Agent tools such as dsh, Claude Code, and Codex work in sessions — when a session ends, the initiative dies. Real work breaks across four seams:

  • Across time — a parked task has no follow-up. Weeks later you can't even reconstruct where it stood.
  • Across projects — one feature touches three repos; three sessions know nothing about each other.
  • Across windows / machines — you can carry the log files, but not "work in progress".
  • Across executors — you and your sub-agents each do a piece, and nobody sees the whole.

dsh-task-center turns the task into the durable unit, hosted outside the harness as a family of plugins. One shared task ledger serves two audiences: you (board for the full picture, acceptance verdicts) and the model (claim tasks, restore context, push forward, report back).

In one line: the board makes work visible, the tools make progress, the alarm clock keeps tasks alive.

It is implemented independently of the harness repo and depends only on its published npm packages (@deepseek-ai/cordis, @deepseek-ai/dsh-*). Full design notes live in docs/design/ (Chinese).

Features

  • Full task lifecycle — five-state machine (todo / in-progress / blocked / awaiting-review / done), append-only event ledger, every change CAS-versioned, everything restored after a restart.
  • Cross-session handover — a new session that claims a task gets the context pack and a PRIOR SESSIONS list injected automatically; you never re-explain the background. Every session id on the board is clickable through to the conversation.
  • Subtask delegation — one task parents many subtasks; different sessions hold and advance them in parallel while the parent aggregates progress.
  • Projects and workspaces — human-managed projects plus a workspace directory stamped at task birth; the board filters four ways (all / project / workspace / ungrouped).
  • Scheduled work — a task can carry wake rules (one-shot / at-time / recurring); when the time comes, a fresh session is spawned, claims the task, and continues. A daily patrol session refreshes the state of every open task. Wake rules and their next fire time render on cards and in the detail dialog.
  • Scheduled sends — from the session page or the board detail, schedule a message (default cont) to be delivered into an existing session at a set time, with quick presets — a task parked mid-flight picks itself up.
  • Quota awareness — when API quota runs out, tasks suspend and release their holders; at the quota reset point they wake and resume (set task-quota resumeOnReset: false to park only and wait for a human). Blocked cards label the reason category (quota / human / …).
  • Crash recovery — when a session holding a task dies (crash or kill), the hold is released automatically and the task becomes claimable again.
  • Automatic extraction — goals, approved plans, and todo tables left in idle sessions birth task candidates for you to confirm and promote; a goal completed with no human response goes straight to awaiting-review; a rejection pushes the reason back into the original conversation and re-claims it for rework.
  • Two frontends — a full-screen web kanban (five columns, filters, blocked pinned on top, detail dialog, creation) and a /task command panel, both reading the same ledger.

Killer flow: mine tasks out of your old chats

Pair it with dsh-chat-import: import your Claude Code / Codex history as resumable dsh sessions, and task-center's extractor will summarize each idle conversation, judge whether it left unfinished work behind (a nameable result, a checkable acceptance, a real intent to continue), and birth the survivors as candidates on the board — no manual triage.

Architecture

Task data uses a double ledger: the authoritative append-only event stream (~/.dsh/storages/task.json) plus session-log receipts (task/change, task/context-injected events) — the ledger survives restarts and keeps sessions consistent; the receipts keep every model input reconstructable from the logs.

PackageRoleWhat it does
taskCore (Service)ctx.tasks: state machine, projects, subtasks, contextPack, events; stamps workspacePath at birth
task-localStorage ProviderOpens the storage domain; backend routes to json/sqlite
tool-taskModel face (Consumer)Seven model tools + prompt section
command-taskHuman face (Consumer)/task command: panel, projects, candidate triage
task-webHuman face (Consumer)Web board: Typert service + browser bundle
task-wakeTime face (Provider)Spawns working sessions on schedule + daily patrol
task-schedTime face (Provider)Scheduled sends: injects a user message into an existing session at the set time (default cont)
task-quotaQuota (Provider)Suspends and releases on QUOTA failures, resumes at reset
task-reaperLiveness (Provider)Releases dead holds, crash recovery
task-sourceExtraction (Provider)Scans idle sessions for candidates; end-of-turn diffs flow back; acceptance births and rejection push-backs
shellStandalone REPLOne-command interactive launcher assembling every plugin
docs/design/   Design archive (product definition, data model, seam specs, plan, extraction layer — Chinese)
packages/      @task-center/* plugin packages (pnpm workspace)

Install

Requires dsh ≥ 0.1.0-rc.8. Prerequisite: the dsh CLI installed globally (npm i -g @deepseek-ai/dsh), with pnpm reachable by dsh plugin (corepack users: corepack enable; if the node directory is not writable, corepack enable --install-directory <dir> and put that dir on PATH).

1. Build (Node does not type-strip .ts under node_modules, so plugins must be loaded as built JS into the profile):

corepack pnpm install && corepack pnpm run build   # produces packages/*/dist

2. Add the packages (from the repo root; all except shell — that one is a standalone REPL launcher that conflicts with dsh run modes):

dsh plugin --profile headless add \
  file:./packages/task file:./packages/task-local file:./packages/tool-task \
  file:./packages/command-task file:./packages/task-wake \
  file:./packages/task-quota file:./packages/task-reaper \
  file:./packages/task-source
dsh plugin --profile web add \
  file:./packages/task file:./packages/task-local file:./packages/tool-task \
  file:./packages/command-task file:./packages/task-wake \
  file:./packages/task-quota file:./packages/task-reaper file:./packages/task-web \
  file:./packages/task-source file:./packages/task-sched

A profile is initialized from its template on first use (web / headless ship templates; other names start from dsh-base).

3. Register the plugin rows in ~/.dsh/profiles/<name>/cordis.patch.yml (not cordis.yml — that one is an empty root). headless needs the three storage rows too; the web bundle ships storage, so do not re-insert them (a duplicate id fails loudly) — just drop the first three lines from the template below.

cordis.patch.yml template (headless)
- insert:
    - id: storage
      name: '@deepseek-ai/dsh-storage'
    - id: storage-json
      name: '@deepseek-ai/dsh-storage-json'
      config:
        root: !!js dshHomePath('storages')   # same root as the web bundle: both profiles share one task ledger
    - id: storage-domain
      name: '@deepseek-ai/dsh-storage-domain'
      config:
        backend: json
        routes: {}
    - id: tasks
      name: '@task-center/task'
      config:
        contextPackByteLimit: 2000
        listDefaultLimit: 20
    - id: task-local
      name: '@task-center/task-local'
      config: {}
    - id: task-source
      name: '@task-center/task-source'
      config:
        pollSeconds: 30
        idleHours: 3
        agent:
          provider: deepseek-official
          model: !!js process.env.TASK_CENTER_MODEL ?? 'deepseek-v4-flash'
        summariesPerTick: 2
        transcriptEvents: 40
    - id: tool-task
      name: '@task-center/tool-task'
      config: {}
    - id: command-task
      name: '@task-center/command-task'
      config:
        staleDays: 3
    - id: task-wake
      name: '@task-center/task-wake'
      config:
        pollSeconds: 30
        agent:
          provider: deepseek-official
          model: !!js process.env.TASK_CENTER_MODEL ?? 'deepseek-v4-flash'
        patrol:
          at: '09:30'
    - id: task-quota
      name: '@task-center/task-quota'
      config: {}
    - id: task-reaper
      name: '@task-center/task-reaper'
      config: {}

4. Validate the composition tree:

dsh --profile <name> --dump-config   # no start, just checks the tree

Usage

export DEEPSEEK_API_KEY=...          # or save it on the web Models page
dsh --profile headless "some task"   # one-shot: build an agent, work, print, exit
dsh web                              # browser UI: task tools for the model, /task command for you

Model tools (tool-task)

ToolEffect
task_createCreate a task (objective / acceptance), optionally under a parent or in a project; the birth workspace is stamped from the session directory
task_claimClaim and receive the full context pack; injects the prior-sessions list
task_updateRecord progress (note / next); clears blocked state
task_reportReport: blocked (with reason) or review (with a self-check against the acceptance criteria)
task_patrolRecord a patrol observation: claims nothing, changes no state, does not refresh the idle clock
task_queryFilter by status / workspace_path / project_id; list live subtasks of a parent
task_projectsList human-managed projects (creation order, with archive flag)

Human actions

Acceptance verdicts (approve / reject), release, archive, block, project CRUD, candidate promotion — all human-only; the model tool face does not register these verbs. The web board and the /task command panel share the same human action face; a conflict refreshes rather than overwrites. On a rejected acceptance, the reason is pushed back into the original conversation as a user message and the task is re-claimed for rework.

Web board (task-web)

Open the full-screen five-column board (todo / in-progress / blocked / awaiting-review / done) from the sidebar footer, with the pending-candidates inbox. Filters: all / project / workspace (birth directory) / ungrouped. The detail dialog shows acceptance criteria, past conversations (clickable through to the session page), subtasks, the context-pack tail, wake rules, and scheduled sends. Blocked cards and details label the reason category (quota / human / …). A ⚠ banner names the open task left untouched longest within staleDays (idle computed over the subtree, freshest wins; delegation in progress does not count as idle).

Append to the web profile's cordis.patch.yml after the command-task row:

    - id: task-web
      name: '@task-center/task-web'
      config:
        staleDays: 3
    - id: task-sched
      name: '@task-center/task-sched'
      config:
        pollSeconds: 30
        agent:
          provider: deepseek-official
          model: !!js process.env.TASK_CENTER_MODEL ?? 'deepseek-v4-flash'

Configuration

FieldPluginDefaultMeaning
contextPackByteLimittaskContext-pack byte limit
listDefaultLimittaskDefault list/query cap
pollSecondstask-source / task-wake / task-sched30Scan / wake / send poll interval
idleHourstask-source3Session idle window
summariesPerTicktask-source2Max summary sessions per tick (install-storm guard)
transcriptEventstask-source40Recent messages carried into the summary prompt
staleDayscommand-task / task-web3Stale-warning threshold in days
patrol.attask-wakeDaily patrol time (e.g. '09:30'; a missed slot is skipped)
agenttask-source / task-wake / task-schedRoute (provider + model) for wake / summary / scheduled-send sessions
resumeOnResettask-quotatrueWhether the reset-point wake rule is set automatically; false parks and releases only, a human resumes

Development

pnpm install
pnpm run build       # full build (includes the web client bundle)
pnpm run test        # build + vitest; real-model e2e self-skips without DEEPSEEK_API_KEY
pnpm run typecheck

Standalone REPL shell (no dsh profile; default ledger ~/.dsh-task-center):

corepack pnpm start                  # or --root <dir> for a custom working root

Gotchas

  • Changed plugin source: after corepack pnpm run build, remove then add the same packages into the profile — pnpm caches file: copies and --force does not refresh them.
  • Changed web client code: the client bundle's verdict and version are cached per process; after a build you must restart dsh web. dist/client.js must exist before the composition row (a declared client package missing its bundle fails the whole web start), so always build before add. The headless profile does not install task-web (no client consumer).
  • Rows inserted by patch must carry an explicit config ({} when empty): the patch path does not normalize a missing config, and a plugin reading config directly in apply (e.g. task-quota) crashes on the spot.
  • Ledger location: dsh profiles share ~/.dsh/storages; the standalone REPL shell defaults to ~/.dsh-task-center. The two never meet.
  • task-sched goes only in the web profile: one send table should have exactly one polling process at a time (two runners would each deliver the same row). headless and the standalone shell omit it; profiles without it can still schedule sends through the web UI.

Roadmap

Implementation status and phases: docs/design/04-plan.md (Chinese). P0/P1, the extraction layer (6a–6f, including acceptance births and rejection push-backs), the three progress-flowback layers, and board history/workspace fusion have all landed. Current milestone: one week of real daily use, iterating on actual pain.

License

MIT