English | 中文
A hot-pluggable DeepSeek Harness (DSH) Web GUI plugin with a Host-authoritative task ledger, real DSH session execution, Host cron scheduling, and optional cross-platform idle-sleep protection. It is mounted through cordis.patch.yml and the profile mechanism and does not modify DSH source code.
Origin: All Tasks is an independent project maintained by cjcox17. It began as a fork of the
dsh-task-boardpackage (@linxin666/dsh-client-ui-task-board) from the zhu1090093659/dsh-web monorepo, cut at the upstreamv0.3.6tag, and has since diverged substantially. It uses its ownall-taskssettings namespace,/api/all-tasksAPI prefix, and$DSH_HOME/all-tasks/ledger directory, and does not migrate data from the original package. Licensed under Apache-2.0 (see LICENSE), matching the upstream package.
- The browser is an asynchronous view; closing the page does not stop Host scheduling or execution settlement.
- Every run applies pinned workspace, agent preset, model selection, and permission before sending the task prompt; each run normally creates a separate DSH session, except maintain-session sequential groups, which reuse one session across members.
- The display may turn off while optional power protection keeps the computer from entering idle system sleep.
- Task board UI: a sidebar entry below New Session shows icon and text in the wide sidebar and an icon in the collapsed rail; the board provides five kanban columns, search, task details, archive/restore, execution history, and links to execution transcripts. Archived tasks are read-only except for restore, delete, and transcript viewing, and cannot run manually or on schedule until restored.
- Workflows (DAG) panel: a single Workflows sidebar entry below All Tasks opens an n8n-style center-column editor for building automation DAGs. A workflow is a graph of three node kinds — events (inbound triggers: HTTP webhook, GitHub webhook, Slack Events), tasks (references to existing board tasks), and actions (result-side side effects: HTTP callback, GitHub write-back, spawn task) — connected by directed edges. The graph is constrained so it must begin with an event and end with an action (tasks sit in the middle, and further events/actions may appear anywhere). The editor is a pannable/zoomable canvas with draggable nodes and port-to-port edge drawing, plus a node inspector for picking references and labels; the DAG is validated (acyclic, event-first, action-last) before it saves. This is the definition layer: workflows are persisted in the Host ledger but do not run yet — the existing event webhooks and the settle→action dispatcher keep working standalone. Event/action node configuration stays in Settings → Plugins → All Tasks, and the node palette is served from the same loopback/proxy fence via
GET /api/all-tasks/integrations. - Hide old tasks: settled (done/failed) work can be hidden — archived off the board into the Archive view (restorable later) — from three places, all feeding the same confirm dialog: each Done and Failed column header carries an archive-icon button that clears the whole column; a task group's banner in those columns carries one that clears that group's settled members in that column; and every settled task card carries the same archive icon overlaid on the card's right edge for hiding a single task. All three affordances show the identical official DSH "Archive session" glyph the sidebar session rows use (icon-only buttons; the details live in the tooltips). Whatever the entry point, the dialog lists the tasks it will hide and offers, checked by default, to also archive their execution sessions in DSH: each hidden task's distinct execution sessions disappear from the DSH session list (their logs and transcripts stay, and the tasks remain restorable) — the one-shot "clear old tasks and their sessions" clean-up. The Host derives the session set from the ledger executions it just archived, never from the browser, and fires the DSH workspace archive best-effort after the commit, exactly like session cancel. Every hide is one confirmed Host transaction (all-or-nothing: one stale card refuses the whole hide rather than half-clearing the column), hides above the per-request cap are sliced into sequential requests, and a refused hide keeps the dialog open showing what still needs hiding.
- Host-authoritative ledger: tasks, schedules, and execution records live in
$DSH_HOME/all-tasks/ledger-v2.json; browser actions become confirmed Host transactions. - Bounded execution history: each task keeps the most recent 20 execution records; the oldest runs are trimmed when a new run starts, so ledger size and write cost stay bounded regardless of how often a task has run.
- Real execution: manual and scheduled runs use the same Host runner, create a fresh session, rename it, apply the agent preset, pin the model selection through
session.selectModel, apply/permission <id>, then queue the task prompt. A maintain-session sequential group instead reuses the group's shared session for every member after the first (see Task groups below), still applying each member's model and permission pins. - Fail-closed pins: a missing workspace, missing or broken preset, rejected model selection, or rejected permission command fails before the task prompt is sent. In a maintain-session group, a member pinned to a workspace/preset different from the shared session's also fails before its prompt.
- Per-task model pin: each task may pin its execution session to a specific provider/model chosen from the host model catalog, plus an optional reasoning-effort level (minimal/low/medium/high or the provider's own value); a blank pin falls back to the deployment default model. When the task pins endpoints, the model dropdown is constrained to the models those endpoints actually serve (union across the pinned list) — a pinned model the endpoints cannot serve stays selectable as a stale row with a hint, so the effective selection is never a silent surprise.
- Auto-generated task titles: creating a task only needs a run prompt — while the title field stays blank, generation waits until you leave the run prompt field (blur), then asks the Host to generate a title from the prompt through a short backend DSH session (the same launch pipeline, one strict "reply with only a title" turn, model pin applied when one is picked), and the field fills in when it lands. Typing alone never fires a request, so a half-finished prompt is never sent to the LLM. A manual title always wins (typed titles are never overwritten), a Regenerate button reruns the generation, and a deterministic fallback (the prompt's first meaningful line, capped at 80 characters) covers failures and instant submits, so creation never blocks on the LLM. The generated title is advisory: the ledger transition still happens through the normal
createaction. The feature is on by default; the Settings → Plugins → All Tasks → Auto-generate task titles switch disables the backend generation while keeping the prompt-line fallback. - Endpoint model-router: named compute endpoints (DeepSeek Official, an LM Studio on the NAS, …). An endpoint is deliberately lean — it names one DSH provider route and narrows which of that provider's models it serves plus a default model; concurrency, token caps, and windows belong to the provider's own settings, not here. Each endpoint also sets that provider's model-request timeouts (idle + total) and carries the local pricing the dashboard cost estimate uses (the official DeepSeek route bills its hard-coded peak/off-peak rates automatically instead). Endpoints are created and edited from the settings card (Settings → Plugins → All Tasks → Endpoints): a full-list editor writes the
all-tasksnamespace live, and the task modal's Endpoints dropdown plus the router pick the change up immediately. The router launches every run through the first endpoint in the task's priority order (or the global default list) that can serve the task's model (its own pin, else the endpoint'sdefaultModel). A run whose endpoints are all blocked waits (queued, nothing billed, survives restarts) and auto-starts when a slot frees, failing only after a configurable max-wait (24 h by default). The off-peak schedule is hard-coded to DeepSeek's official rule (peak 01:00–04:00 and 06:00–10:00 UTC Mon–Fri; every other hour and all of Sat/Sun off-peak, per the 2026-08-23 pricing change) and is not configurable. - Task groups: named sets of tasks with shared execution policy, shown as a group inside every kanban column under its own banner (empty groups stay visible in the To Do column, so a fresh group is immediately findable and manageable). Groups are workspace-scoped: a group belongs to the workspace it was created in and accepts only tasks pinned to that workspace as members (a group without a workspace takes only unassigned tasks), so the same group name can exist in several workspaces with independent settings. A group has an execution mode — sequential (one member at a time, in the group's member order) or parallel (up to a configurable cap, blank = unlimited) — that gates every launch of a member (manual, cron, or router auto-start). A group may pin its own priority-ordered endpoint list (the member's own pin wins, then the group's, then the global default), an allowed-hours window plus an off-peak-only flag (the hard-coded DeepSeek schedule above), and an optional group cron. When the group cron is armed, members inherit it and their own schedules are ignored; when a member settles, the next runnable member in order starts automatically. Blocked manual runs show why they wait (waiting for a group slot / waiting for the allowed window / waiting for endpoint) and auto-start when a slot or window frees, exactly like the endpoint queue. A running member keeps its group's capacity slot: it cannot be moved to another group (or ungrouped) until its execution settles, so the old group can never start a second member while the first is still running. The board stops work directly: every running member card carries its own Stop button, and the group banner's Stop group cancels all running members and marks the group stopped (no member launches — manual runs, member crons, and the group cron are all refused) until its Resume button clears the flag — Resume counts as starting the group: it releases every member still waiting on an explicit start, so with the members back in To Do the whole sequence restarts from that one press. Dragging the group banner onto the Backlog or To Do column moves the whole group at once instead of dragging members one by one, and a manual move never starts the group — it waits for Start group (or its cron), so dragging a completed group back for a re-run can't kick off a run by accident.
- Approval gate: every task carries an approval state. Tasks created manually are approved by default; programmatic creation (the protocol
createaction) may mint a task unapproved. An unapproved task can never be run by any means — manual Run/Rerun, its own cron, and group auto-advance are all refused until it is approved again (a group sequence skips it and continues with the next member). Unapproved tasks stay fully manageable: they render with a Not approved badge, can be moved/edited/grouped, and the header's Unapproved only filter shows exactly what needs approval for the day. Approve with one click from the card (✓), the task detail, or the group member row; the task detail also offers Unapprove. A queued run whose task is unapproved while waiting is cancelled (lands in the Failed column), and a task cron held while unapproved rolls forward to its next occurrence instead of firing late. - Agent task tools: any DSH agent session can create and inspect board tasks through the native tools
task_create,task_list, andtask_get(registered onctx.toolswhenever the board is enabled).task_createqueues work only — it never runs anything: the created task defaults to unapproved (a human must approve it on the board before it can ever run), carries the Agent origin badge, and supports the full execution-target set (workspace, mode, model, endpoints, group, permission, schedule);approved: trueis accepted only for the explicit "make it immediately runnable" case.task_listreturns compact on-board rows (id, title, status, approval, origin, schedule, running state) so the agent can avoid duplicates, andtask_getreturns one task's full detail including its run prompt and execution history. The origin is display-only and never gates anything. - Pause & continue: every running task can be paused — the session's active turn is cancelled but the session is kept alive, the run stays open marked Paused, and nothing settles or launches for it — and continued later, which re-queues the task prompt in the same session so the agent resumes with its full history. The controls live on the running card (⏸ / ▶), the open execution row in the task detail next to view session, and the detail footer. Pausing scales to whole task groups (Pause group halts every running member and blocks member launches until Continue group) and to entire workspaces (the ⏸ on each workspace overview row pauses every task pinned to that workspace and blocks new launches — including crons, queued runs, and group auto-advance — until ▶ resumes them; the All tasks row pauses the whole board). A paused workspace shows a Paused pill and its tasks show waiting for the workspace to be continued when queued. Pause is a soft hold: Stop is still the hard stop that cancels the run into the Failed column.
- Waiting for your answer: while a task's run is executing, its session's agent may ask you a question (
ask_user_question) and its turn blocks until you answer — from the board that run would otherwise look identical to one quietly working. The board now tells them apart live: the running card swaps Running… for a Waiting for your answer pill (blue accent; the question text rides in the card tooltip), and the open execution row in the task detail shows the same badge. Clicking a waiting card takes you straight to that session to answer it — every other card still opens the task detail. The state is a volatile Host-side overlay: the Host watches the DSH mux stream'squestion/requested/question/resolvedframes for its execution sessions and carries the open asks on the snapshot and SSE frames without touching the durable ledger — an ask opening or closing never bumps a revision, a question in a session the board does not own is never advertised, and a Host restart recovers still-pending questions from the mux replay. - Workspace overview list: the board opens on a Monday.com-style board table — a column header (Workspace / To do / Pending / Working / Scheduled / Finished / Failed / Total) above one card per workspace: every workspace is its own card (surface, border, rounded corners, breathing room between cards) with a color-coded avatar and right-aligned tabular counts, so workspaces read as distinct boards instead of items crammed inside a single shared box, plus an All tasks card aggregating every workspace (including unassigned tasks). Clicking a row opens that workspace's kanban; the ▸ chevron expands the card inline into its groups (colored section headers with a name pill, mode badge, member count, and a per-group collapse toggle) and its task rows (status dot, title, aligned status label and badges), and clicking a task row opens the task detail; the ⚙ button opens the workspace default-settings editor. The kanban is always workspace-scoped now, the header's back button returns to the overview, and the old workspace dropdown is gone. A scoped view shows that workspace's pinned tasks per column, then an Unassigned section for tasks without a pin (they fall back to the recent workspace at run time), so nothing disappears from any view; only that workspace's groups appear in the main columns, and unassigned-scope groups live inside the Unassigned section. A workspace deleted from the runtime list (removed in the sidebar) leaves the board together with its tasks and groups once the workspace baseline has loaded — no vanished-workspace row is synthesized, and its pinned tasks are filtered out of the overview, the kanban, and the dashboard. The ledger keeps the pins untouched, so the tasks are still recorded; they are simply not shown.
- Board dashboard & workspace controls: the landing view opens with a summary strip — tasks (todo / pending / running / queued), completed (with success rate), failed (with total runs), scheduled + groups, token totals, and an estimated cost. Token totals are captured from each session's
assistant/messageusage at settlement (input/output/cache/reasoning), so only runs started after this change carry them. The cost is an estimate per execution: runs through the official DeepSeek route (and unrouted runs pinned to it) are billed at DeepSeek's official peak/off-peak rates (peak 01:00–04:00 and 06:00–10:00 UTC Mon–Fri; every other hour and all weekend at half price, per the 2026-08-23 pricing change — the rate table is hard-coded like the schedule), chosen by the instant the run launched, with cached input billed at the cache-hit rate and cache writes at the cache-miss rate; runs through any other endpoint use that endpoint's own USD-per-1M-token prices (set in the Endpoints editor); runs with no applicable rate show "—". A usage retention window (Settings → Plugins → All Tasks → Keep token usage for the last N hours) narrows both the token totals and the cost estimate to runs settled within the window — set to 24 and only the last day counts (0 = all time). It is a display window only: the ledger keeps every execution record, nothing is pruned, the Tokens/Cost cards show a "last N h" prefix so the narrowed totals read clearly, and the usage charts below honor the same window. Workspace rows are expanded by default (foldable per group and per workspace), and each row carries ▶ run and ⏹ stop controls plus the workspace ⏸ pause (which becomes ▶ continue while paused): run starts its ungrouped todo tasks (never backlog) plus its non-stopped groups, stop cancels its running ungrouped tasks and groups, and the pause is the board-level soft pause that halts every running task pinned to the workspace and blocks new launches until continued (see Pause & continue above). - Dashboard usage charts: below the summary cards, the landing view also shows two graphs — Cost over time and Tokens over time — driven by one shared Hourly / Daily / Weekly dropdown: the last 24 clock hours, the last 14 local days, or the last 8 Monday-aligned weeks. Every execution that reported usage is bucketed by its run start time (browser-local time); tokens stack billed input (uncached + cache read/write), output, and reasoning with a color legend, and cost bills each execution against the endpoint it ran through (the official DeepSeek route at its hard-coded peak/off-peak rates, any other endpoint at its own configured prices) — both mirroring the summary cards (the cost panel notes the missing pricing when runs carry usage but no applicable rate). Bars are dependency-free HTML/CSS columns with native tooltips, so the graphs need no chart library; only runs started after token capture shipped carry usage, exactly like the token totals card. The charts follow the usage retention window too (Settings → Plugins → All Tasks → Keep token usage for the last N hours): executions settled outside the window don't count, buckets that end before it are clipped out — a daily or weekly view under a short retention shows only the buckets the window reaches — and the panel hint switches to the retention window (e.g. "last 24 h") whenever the retention shortens the look-back.
- Workspace default settings: each workspace can carry execution defaults (agent preset, model + reasoning effort, endpoint order, permission, and a "new tasks start unapproved" switch) that pre-fill the new-task dialog inside that workspace's kanban — ⚙ entry on the overview row and in the kanban header. Defaults are persisted by the Host ledger (keyed by workspace id; an all-blank edit removes the entry). A task's own execution targets win at run time; any target the task leaves blank falls back to its workspace's default, and only then to the deployment default — so tasks created before defaults existed, or with blank fields, still run with the workspace's settings.
- Per-endpoint model timeouts: DSH aborts a model request when no new content arrives for 300 s (the stream-idle watchdog default), which a local LLM recomputing a long message chain or generating slowly trips even while the backend keeps producing tokens. Each endpoint's editor row carries that provider route's idle timeout (and, for custom/local
llm-pi-airoutes, the optional total-request bound); saving writes the provider's own DSH settings, applied live to the next request through that provider, chat and task runs alike. - Host scheduler: 5-field cron supports
*,*/n, ranges, comma lists, Sunday0/7, and standard day-of-month/day-of-week OR semantics in the Host local time zone. - Deterministic recovery: a running execution with a recorded session is observed after restart; an interrupted start without a session id is cancelled and is not resent. A cancelled run (a user stop, or a session that vanished mid-run) settles as cancelled and the task moves to the Failed column — a stop is never a success.
- Live synchronization: mutations return a full revisioned snapshot; SSE announces revision, scheduler, and power changes, while reconnect and page visibility recovery fetch a full snapshot.
- Optional idle-sleep protection: off by default; when enabled it covers every running DSH session, enabled non-archived all-tasks schedules, and unknown session state.
- Session-view timestamps and token counts: the main session view shows what time each message and tool call ran at. User-message start clocks and the assistant turn end/duration labels (which DSH hides until the row is hovered) stay visible, every tool row carries an always-visible
HH:MM:SS · durationchip read from the session event timestamps (aM/DorY/M/Dprefix is added for older days), and each turn tail shows that turn's input/output token counts (Input 1.2K tok · Output 350 tok) read from the assistant usage event. On by default; the Settings → Plugins → All Tasks → Show times and token counts in the session view switch restores the official hover-only behavior. - Session-row archive buttons: the DSH session list gains a one-click Archive affordance for cleaning up large numbers of sessions. Hovering a session row reveals an archive icon next to the official "…" menu (which stays for Rename/Fork), and a single click archives that session — the same operation as its "Archive session" menu item, writing the registry-global archive set the board's hide-tasks flow also uses, so the session disappears from the DSH session list while its log and workspace slot remain. The icon appears only when the row can be tied to a session with certainty — a unique title, or same-name sessions inside a workspace section whose rendered rows provably match the recorded account order (every row title matches the member at the same position, with at least one globally unique-title row as the anchor) — so archiving never hits the wrong session; same-name rows in the flat "In one list" view, drag-reordered or collapsed/truncated sections, and ungrouped sections keep only the official menu. On by default; the Settings → Plugins → All Tasks → Show one-click archive buttons in the session list switch restores the official "…"-only behavior.
- System-prompt injection: the Host registers a
plugin:all-taskssection (order 200) throughSystemPrompt.section, and the all-tasks settings can disable the announcement without disabling the board. The guidance also reminds agents to close any visibletodo_writeplan before the final answer.
src/index.tsmounts the Host service through the official@deepseek-ai/dsh-host-apiproxyand@deepseek-ai/dsh-host-webserverSDKs.src/host-ledger.tsserializes actions and persists{ schemaVersion: 2, revision, tasks, groups, workspaceDefaults, workspacePaused, scheduler, recentRequests }through a temporary file plus atomic rename.src/host-service.tsowns cron ticks, missed-trigger skipping, runner launch, restart reconciliation, and power reasons.src/client/host-api.tsimports legacy browser data once, submits idempotent actions, and treats Host snapshots as the only confirmed UI state.- Same-origin endpoints are
GET /api/all-tasks/state,GET /api/all-tasks/integrations(the Workflows node palette: registered event sources and actions),GET /api/all-tasks/events,POST /api/all-tasks/action, andPOST /api/all-tasks/title-suggest(the title-generation RPC; advisory only — it never touches the ledger). - Every endpoint requires a browser same-origin marker. Direct access is restricted to the DSH loopback origin; an authenticated same-host reverse proxy must use an explicit Host allowlist and a server-injected token. POST requests additionally require JSON. Ordinary actions are limited to 64 KiB and import to 2 MiB. The action union has no command, executable path, shell text, or arbitrary argument field.
Install from this repo, then restart dsh web. The first pnpm build of a
git-hosted plugin is blocked by pnpm's script policy until allowed — run the
install and add the exact key pnpm prints under allowBuilds in
$DSH_HOME/profiles/web/pnpm-workspace.yaml, then re-run:
dsh plugin --profile web remove @linxin666/dsh-client-ui-task-board # only if the original is still installed
dsh plugin --profile web add github:cjcox17/all-tasksFor local development:
git clone https://github.com/cjcox17/all-tasks.git
cd all-tasks
pnpm install
pnpm build
dsh plugin --profile web add link:$(pwd)| Key | Default | Behavior |
|---|---|---|
enabled |
true |
Enables the Host service and browser board. |
announceToAgent |
false |
Opt-in: when true, adds the all-tasks guidance section to agent system prompts. |
preventIdleSleep |
false |
Holds one system idle-sleep assertion while any DSH session runs, any schedule is enabled, or session state is unknown. |
autoTitle |
true |
When the new-task title is blank, generates one from the run prompt through a short backend session once you leave the prompt field (blur) — typing alone never asks (the prompt's first line is the fallback; disabled means no generation, the prompt-line fallback still applies at submit). |
sessionTimestamps |
true |
Shows what time each message and tool call ran at in the main session view — message clocks stay visible, every tool row gets a start-time + duration chip — and each turn tail shows that turn's input/output token counts. |
sessionArchiveButtons |
true |
Adds a one-click archive icon next to the "…" menu on DSH session-list rows (revealed on hover). Shown only when the row's session is identifiable with certainty (unique title, or positionally verified inside a workspace section); rows without a certain match keep the official "…"-menu-only archive. |
trustedProxyHosts |
[] |
Canonical host[:port] authorities accepted only through the authenticated loopback reverse-proxy path. |
proxyTokenEnv |
DSH_ALL_TASKS_PROXY_TOKEN |
Environment variable containing the reverse-proxy token; the token itself is never stored in plugin config. |
endpointMaxWaitHours |
24 |
How long a queued run may wait for an eligible endpoint before it settles failed. |
usageRetentionHours |
0 |
Dashboard usage window in hours (0 = all time): the token totals and the cost estimate only count runs settled within the last N hours (24 = last day). A display window only — the ledger keeps every execution record — and the usage charts below honor the same window. |
defaultEndpoints |
[] |
Ordered endpoints used by tasks without explicit endpoint pins. |
endpoints |
[] |
Named compute endpoints the router routes tasks through (see below). |
The off-peak schedule is hard-coded to DeepSeek's official rule (peak 01:00–04:00 and 06:00–10:00 UTC Mon–Fri; every other hour and all of Sat/Sun off-peak, per the 2026-08-23 pricing change) and is deliberately not configurable — DeepSeek owns these hours, not the user. Groups use it through their Off-peak only checkbox.
Direct browser access remains limited to the DSH loopback origin. For a same-host authenticated reverse proxy, bind DSH Web to loopback, set trustedProxyHosts, place a high-entropy token in the environment variable selected by proxyTokenEnv, and configure the proxy to replace (not forward from the client) X-Dsh-All-Tasks-Proxy-Token after it authenticates the request. The proxy Host must be allowlisted, and the browser Origin must have that same authority. Restart the Host after changing these composition-level proxy settings.
Endpoints live under the all-tasks settings namespace. The settings card (Settings → Plugins → All Tasks → Endpoints) is the normal editor: it lists every endpoint, adds/removes/reorders them, picks a provider (limited to the known llm provider routes), narrows the served models to that provider's model list, picks a default model among them, sets the provider's request timeouts (idle + total), and — for local providers — sets the endpoint's own pricing for the dashboard cost estimate (input and output USD per 1M tokens; the official DeepSeek route shows a note instead, because it bills its hard-coded official peak/off-peak rates automatically). Save writes the whole list — the task modal's Endpoints dropdown and the router reload live, no restart. The same values are stored as YAML under all-tasks and may be edited directly too (the router reloads them live as well); the timeout fields write through to the provider route's own settings (llm-pi-ai / llm-deepseek), which is the only place DSH honors them:
all-tasks:
defaultEndpoints: [deepseek-official]
endpoints:
- id: deepseek-official
name: DeepSeek Official
provider: deepseek-official # an llm provider route id
models: [] # empty = all models of the provider
defaultModel: deepseek-chat # official peak/off-peak rates apply automatically
- id: lm-studio-nas
name: LM Studio (NAS)
provider: lm-studio
models: [qwen/qwen3.8-27b] # narrowed to the provider's model list
defaultModel: qwen/qwen3.8-27b
costPerMillionInputTokens: 0.10 # USD per 1M tokens; 0/absent = not configured
costPerMillionOutputTokens: 0.20A task pins endpoints in priority order (new-task modal / task detail → Endpoints); the router uses the first one that can serve the task's model — the per-task model pin when the endpoint serves it, otherwise the endpoint's defaultModel. While every candidate is blocked the task shows waiting for endpoint and starts automatically when the model becomes servable. Provider-level concerns (concurrency, token caps, allowed hours, off-peak windows) belong to the provider's own settings, not the endpoint; the only per-endpoint tunables beyond the selection are the model request timeouts (idle + total) and the local pricing above.
Groups are workspace-scoped entities created in the UI (header + New Group inside a workspace's kanban — or from the All overview for an unassigned-scope group), persisted in the Host ledger, and shown as a banner grouping their member cards inside every kanban column. A group belongs to the workspace it was created in and never changes scope; the same name can exist in several workspaces with independent settings. Empty groups (no members anywhere) still render in the To Do column so a fresh group is immediately visible and manageable. While the sequence is active the banner carries live status pills — Running (a member's session is executing, with a count when several run at once) and Pending (members are held before launch — queued for a group slot, the allowed window, or an endpoint — with the reason in the tooltip) — so a group that is mid-run or waiting is never mistaken for an idle one from any column:
- Membership: a task belongs to at most one group, and only to a group of its own workspace scope — a task pinned to a workspace can join that workspace's groups, an unassigned task can join unassigned-scope groups, and a task moved to another workspace leaves its old workspace's group automatically. Pick the group in the new-task modal or the task detail (Groups section), or drag the card straight onto a group's banner/section to join (assigned to the end of the group's order). Dragging a member onto the column background (outside every group) leaves the group. Assigning appends the member to the group's order; removing ungroups it. Deleting a group ungroups its members (their tasks stay) and is refused while any member has an open run. Existing ledger rows from before this change migrate on load: a group without a scope adopts its members' workspace when they all share one, and mixed groups flatten to the unassigned scope (foreign members are ungrouped).
- Joining a running group: a task that joins a group whose sequence has already started — created into it, edited into it, or dragged onto it — waits for an explicit start: the auto-advance chain skips it so it never launches on its own (the wait is internal bookkeeping — no badge). The member's Run button, the group's Start group button, Resume on a stopped group, Continue group on a paused group, and the next group-cron fire are all explicit starts that release it and launch it. Joining a fresh, never-run group behaves as before (nothing to wait for). A task waiting on an explicit start stays fully manually startable either way.
- Execution mode: sequential runs one member at a time, in the group's member order — when a member settles, the next runnable member (backlog/todo, not archived, approved, not held) starts automatically. Parallel runs up to the configured
maxParallelat once (blank = unlimited). Every launch of a member — manual, group cron, or router auto-start — respects the group's capacity and window, and an unapproved member is never launched (the sequence skips it and continues with the next approved member). - Final step (the merge step): the group editor can designate one member as the group's Final step — the fan-in of a parallel build. The designated member never launches with the others (it is excluded from the parallel burst, from auto-advance, and from manual run) until every other member has settled: each is in the Finished or Failed column with no open run (archived members are out of the sequence). Once the gate opens, the advance pass starts it automatically — or Start group / a manual run once the members are settled. Require all other members to succeed makes any failure block the merge until the failed member is rerun successfully or archived; without it, any settled outcome opens the gate. The card carries a Final step badge (with a waiting for group members hint while gated), the banner shows a Final step · waiting pill, and the task detail notes the role. Designating a member and then removing it from the group clears the designation.
- Maintain session (sequential only): instead of a fresh session per task, the group keeps one DSH session — the first launched member creates it, every later member continues the same conversation, so the context carries across tasks. The session's workspace and agent preset are fixed by the first member; a later member pinned to a different workspace/preset fails closed before its prompt (the session cannot switch composition). Each member's model and permission pins still apply per run. If the shared session no longer exists (deleted out-of-band), the next member starts a fresh session and the sequence self-heals.
- Compact between members (sequential only, with Maintain session): run
/compacton the shared session before each member's prompt, condensing the accumulated conversation into a summary — the context stays within the window without losing its key content./compactis dispatched through the same command seam as/permission; an unavailable or failed compaction fails the member's run closed, so a misconfigured deployment is visible rather than silently skipping. - Endpoints: a group may pin a priority-ordered endpoint list (group editor → Endpoints). The effective list for a member is the member's own pin, then the group's, then the global
defaultEndpoints. - Window: a group may restrict launches to
allowedHours(host-local time) and/or to the hard-coded DeepSeek off-peak schedule (Off-peak only — peak 01:00–04:00 and 06:00–10:00 UTC Mon–Fri; weekends fully off-peak). - Schedule: a group may arm its own cron. While armed, members inherit it — their own schedules are ignored (the detail view shows a hint) — and the cron starts the sequence (first runnable member; the chain then continues as members settle).
- Stop & resume: while any member is running, the group banner shows Stop group — it cancels every running member's session (each settles as
cancelledand its task lands in the Failed column) and marks the group stopped. A stopped group launches nothing: manual member runs, member crons, and the group cron are all refused, and the banner shows a Stopped badge plus a Resume button. Each running member card also carries its own Stop button, so one member can be stopped without touching the rest. Resume is an explicit start: besides clearing the stopped flag it releases every member still waiting on one, so once the members are back in a manual column (drag the group over) the sequence restarts from that single press — no second press is needed. - Pause & continue: next to Stop, the group banner offers Pause group (⏸) — it pauses every open member execution (sessions kept alive, runs stay open) and marks the group paused, so no member launches by any means (manual runs, member crons, the group cron, auto-advance) until Continue group (▶) re-prompts the paused members' sessions in place and clears the flag — an explicit start that also releases members waiting on one, so the resumed sequence covers work that joined while it ran. The banner shows a Paused badge while paused. Members can be paused individually from their cards (⏸ on a running member, ▶ on a paused one) without touching the rest, exactly like the per-member Stop button.
- Order: drag a card above or below its siblings to reorder — ungrouped cards reorder inside their column, group members reorder within the group (both persisted in the ledger). Editing a task (title, description, prompt, execution targets) never changes its place.
- Move the whole group: grab anywhere on the group banner (the grip at its start marks it) and drag it onto the Backlog or To Do column to move every member at once instead of dragging cards one by one — the natural way to send a failed group back for a re-run. Only an open member execution (running or queued) blocks the move; a settled group moves freely even when it is stopped or paused (those flags still block launches until resumed). A manual move never starts the group: members land in the manual column waiting on an explicit start (Start group, the group cron, or a member Run), so a completed group dragged back for a re-run can never start by accident. A floating ghost follows the pointer during every drag, with an insertion line showing where a drop will land.
- Waiting: a manual run blocked by capacity shows waiting for a group slot; blocked by the group window, waiting for the allowed window; blocked by endpoints, waiting for endpoint. All three queue host-side (nothing billed, survive restarts) and auto-start when a slot or window frees, failing only after
endpointMaxWaitHours.
Every task has an approval state: approved (the default) or unapproved. Only the explicit unapproved state is persisted (approved: false on the task record); legacy tasks without the field are approved. The state is a pure gate — it never removes a task from the board, and moves, edits, grouping, and archiving stay available either way.
- Default: tasks created manually (the new-task dialog) are approved. Programmatic creation through the protocol
createaction may passapproved: falseto mint a task unapproved — the intended flow for anything that should wait for a human's sign-off (e.g. tasks created by another tool for the day's queue). - Never runs: an unapproved task cannot be run by any means. Manual Run/Rerun is refused by the Host ledger (
task is not approved); its own cron is held — the occurrence rolls forward to the next scheduled instant without launching, so approving later resumes the cadence; group auto-advance and the group cron skip it and continue with the next approved member; and a run already queued for an endpoint is cancelled when its task is unapproved while waiting (it lands in the Failed column, like any stop). - See and approve: unapproved cards show a Not approved badge, and the board header's Unapproved only toggle filters the whole board to exactly the tasks waiting for approval — a one-glance list for the day. Approve with a single click from the card (✓) or the group member row, or from the task detail, which also offers Unapprove to gate a task again.
Every DSH agent session can create and inspect board tasks through native tools registered on ctx.tools whenever the board is enabled — an AI session can queue work for other sessions without opening the browser:
task_create— the only write tool. It queues a task (never runs anything): the created task is minted unapproved by default — it can never run by any means until a human approves it on the board — and carries the Agent origin badge. It accepts the full execution-target set (workspaceId,mode,modelwithreasoningEffort,endpoints,groupId,permission, an optionalschedule, andapproved).approved: trueis honored only when the caller explicitly wants the task immediately runnable. Validation runs on the Host ledger exactly like the browser path: a blank title, an unknown or workspace-mismatched group, and an invalid cron all fail the call closed.task_list— read-only. Returns the on-board tasks (archived excluded) as compact rows — id, title, status, workspace/group, approval, origin, schedule, and running state — with an optionalstatusfilter, so an agent checks for duplicates before creating.task_get— read-only. Returns one task's full detail: title, description, run prompt, execution targets, approval state, origin, schedule, and execution history summary. Throws for an unknown id.
The tools are deliberately read/create only: update, run, approve, and delete stay human board actions. A task an agent creates appears with an Agent badge on the card and an origin line in the task detail (the same badge pattern covers Webhook and API origins); the origin is display-only and never gates anything. The guidance section (gated on announceToAgent) mentions the tools; agents also discover them through their tool list either way. Creation through the tools goes through the same fail-closed create action as the browser — a disabled board refuses it.
The board opens on a workspace overview list: a column header (Workspace / To do / Pending / Working / Scheduled / Finished / Failed / Total) above one card per workspace — every workspace is its own card (surface, border, rounded corners, breathing room between cards) with a color-coded avatar and right-aligned tabular counts, so workspaces read as distinct boards instead of items crammed inside a single shared box — plus an All tasks card aggregating every workspace (including unassigned tasks). The ▸ chevron expands the card inline into its groups (colored section headers with a name pill, mode badge, member count, and a per-group collapse toggle) and its task rows (status dot, title, aligned status label and badges), and clicking a task row opens the task detail. Clicking a row opens that workspace's kanban — the kanban is always scoped to that workspace (the task's workspaceId pin — the workspace its session runs in), and the All tasks row opens the unscoped general overview. The kanban header's back button returns to the overview list; the old Workspace select is gone. In a scoped view each column shows that workspace's pinned tasks first, then an Unassigned section for tasks without a pin (they fall back to the recent workspace at run time), so nothing disappears from any view. Groups are workspace-scoped: a workspace's kanban shows only the groups of that workspace in the main columns, and unassigned-scope groups render inside its Unassigned section. The scoping also applies to the archive view and composes with the text search; it is view state only — no task, ledger, or protocol change is involved. A workspace deleted from the runtime list (removed in the sidebar) leaves the board together with its tasks and groups once the workspace baseline has loaded: the overview never synthesizes a vanished-workspace row, and the deleted workspace's pinned tasks are filtered out of the overview counts, the kanban (columns and archive), and the dashboard. The ledger keeps the pins untouched — the tasks remain recorded, they are just no longer shown. Until the workspace baseline has loaded (startup / reconnect), the filter is suspended so a not-yet-loaded list can never blank the board.
Each workspace row (and the All tasks row) also carries a pause control: ⏸ pauses the whole workspace — every open execution of its tasks is paused and the workspace is marked paused, so nothing launches (manual runs, task crons, queued runs, group auto-advance) until ▶ continues it, which re-prompts the paused sessions and clears the flag. A paused row shows a Paused pill; the workspace kanban's tasks show their individual Paused badges, and queued runs show waiting for the workspace to be continued.
Each workspace can carry execution defaults that pre-fill the new-task dialog when a task is created inside that workspace's kanban (the workspace is pre-selected there):
- Mode: the new task's agent preset (blank = deployment default).
- Model + reasoning effort: the model selection pinned to the new task (blank = deployment default).
- Endpoints: the new task's endpoint priority order (blank = no pin).
- Permission: the
/permissionpreset applied to the new task (blank = session default). - New tasks start unapproved: when checked, new tasks in this workspace start unapproved and cannot run by any means until approved.
Defaults are persisted by the Host ledger: the workspaceDefaults map keyed by workspace id stores only non-empty records, edited through the protocol set-workspace-defaults action (patch semantics: null clears a field, and clearing every field removes the workspace's record). The ⚙ entry lives on the overview row and in the kanban header.
At execution time the workspace defaults act as the fallback layer beneath the task's own targets: the task's own mode/model/endpoints/permission win when set, a blank target resolves to the workspace's default, and only a target blank on both sides uses the deployment default. This applies to every launch path — manual runs, reruns, crons, queued runs, and group auto-advance — and the workspace-default mode/model/permission pass through the same fail-closed checks as an explicit task pin (a broken default preset or a rejected default model selection fails the run before the prompt). The approved default is deliberately excluded: it only gates creation (new tasks start unapproved), never an existing task's own approval state. The task pickers label their blank "Workspace default" mode/model option with the effective default's name (the workspace's default when one exists, else the deployment default's preset), and the task detail's execution settings show a "Workspace default: …" hint next to endpoint/permission fields the task leaves blank but the workspace supplies.
DSH aborts a model request when no new content arrives for 300 seconds (the stream-idle watchdog default). A local LLM recomputing a long message chain, or one with low compute speed, can exceed that window even though the backend is still generating tokens, turning executions into continuous failures. The endpoint editor (Settings → Plugins → All Tasks → Endpoints) carries each endpoint's provider-route timeout fields:
- Idle timeout (seconds): abort the request when no new content block arrives for this long; default 300.
- Total request timeout (seconds): overall request bound (custom/local
llm-pi-aiproviders only); blank keeps the backend default.
Saving the endpoint list writes the provider's own DSH settings — streamIdleTimeoutMs and, for pi-ai routes, timeoutMs, e.g. llm-pi-ai.providers.lm-studio.streamIdleTimeoutMs in ~/.dsh/settings.yaml — validated by DSH's provider schema and applied live to the very next request through that provider, chat and task runs alike. Timeouts are per-provider (all models on a route share them) because that is DSH's native granularity; the board only edits the same settings the Models page edits, so no Host, ledger, or protocol change is involved.
On macOS the backend starts /usr/bin/caffeinate -i -w <host-pid> and never requests -d. On Windows it starts the absolute Windows PowerShell under SystemRoot with a fixed helper that requests only ES_CONTINUOUS | ES_SYSTEM_REQUIRED; it never requests ES_DISPLAY_REQUIRED, changes a power plan, or requires administrator privileges. On Linux it starts a systemd-logind idle block inhibitor only from /usr/bin/systemd-inhibit or /bin/systemd-inhibit; it does not request sleep, handle-lid-switch, or a display/screensaver inhibitor. A Linux host without systemd-logind reports unsupported or a visible error and does not start a desktop-specific fallback. Other platforms report unsupported.
- The v2 ledger is
$DSH_HOME/all-tasks/ledger-v2.json. New POSIX files use mode0600; Windows inherits the user directory ACL. - A corrupt v2 file is moved to a collision-resistant
ledger-v2.json.corrupt-*name and the Host starts with an empty ledger plus a visible scheduler error. The corrupt bytes are not overwritten. - All Tasks starts fresh and does not import the original
dsh-task-boardpackage's browser data. The plugin's own legacy browser key isdsh.allTasks.v1; on the first page load for an origin any data under it is imported by stable source and request ids. Tasks merge by id, strictly newer browser top-level fields win, equal timestamps keep Host fields, and execution records merge by execution id. - The most recent 256 request ids and SHA-256 action fingerprints are stored with the ledger, so a retried mutation remains idempotent after a Host restart without duplicating full action payloads.
- The import marker
dsh.allTasks.v2.hostImportedstores the confirmed Host ledger generation only after import succeeds. A new or recovered ledger generation is offered the retained v1 data again. The v1 localStorage value remains untouched as a read-only rollback copy. - One Host process owns a all-tasks ledger directory at a time through
$DSH_HOME/all-tasks/ledger-v2.lock; a second Host using the same DSH home fails closed instead of concurrently writing the ledger.
- The plugin stays inside the existing DSH Web deployment and network boundary and emits no permissive CORS headers. State, action, and SSE routes share the same access fence; bare local command-line requests are not accepted as browser requests.
- All mutation payloads use a strict, versioned discriminated union; schedule-owned timestamps and execution outcomes cannot be written by the browser.
- Workspace, preset, model selection, permission, cron, task status, and imported records are validated again on the Host.
- A task prompt is data sent to a DSH agent session. The protocol does not accept shell commands, PowerShell bodies, executable paths, or configurable helper arguments.
- Power helpers use fixed executable paths, fixed arguments,
shell: false, and bounded retry delays of 1, 2, 5, 10, then 30 seconds. The Linux helper follows the Host stdin lifetime so the systemd inhibitor is released automatically after an abnormal Host exit.
Node 20 or newer and the official NPM SDK packages are required; no DSH source checkout is used.
pnpm install
pnpm typecheck
pnpm test
pnpm buildSet DSH_POWER_SMOKE=1 to opt into the native helper smoke test on Windows, macOS, or Linux. It starts the fixed helper, waits for readiness, releases it in cleanup, and confirms process exit without changing the system power plan. Linux first probes systemd-logind with a bounded timeout; without a usable system bus the native portion is skipped while pure logic tests remain available.
- Mount the package, restart
dsh web, open the task board, and confirm the Host time zone and power status are visible. - Create and edit a task; refresh or open a second same-origin tab and confirm both show the updated task (the internal ledger revision is not shown in the UI).
- Run a task with pinned workspace, preset, model (plus a reasoning-effort level), and permission; confirm a new session appears and the task settles from its
turn/endhistory. - Open the settings card (Settings → Plugins → All Tasks) → Endpoints, add an endpoint (e.g.
lm-studioserving your local provider), pick the provider, tick one of its models, set the default model, and confirm the new-task modal's Endpoints dropdown lists it immediately. Then pin that endpoint on a task whose model it cannot serve and run it: the task shows waiting for endpoint, no session is created, and it auto-starts when the endpoint can serve the model (e.g. after you edit the endpoint to add the model or a default model). Also confirm the execution row shows which endpoint ran it. - Create a sequential group with two backlog members; run the first member and confirm the second starts automatically once the first settles. While a member runs, confirm the group banner shows the Running pill (and the Pending pill for any member queued behind it, with the wait reason in the pill's tooltip) in every column the group appears in. Add a third member and run it while the first is still running: it shows waiting for a group slot and launches after the settle. Then, while the group is running, add a fourth member (create it into the group or drag an existing card onto the banner): confirm it does NOT start when the running members settle (the wait is internal — no badge); run it with its own Run button (or the group's Start group button) and confirm it launches. Let the group complete, drag the whole group back to To Do and confirm nothing starts from the move alone, then press the group's Start group and confirm the whole sequence re-runs from that one press.
- Create a sequential group, tick Maintain one session across members, and add two backlog members. Run the first and confirm exactly one session is created; when it settles, confirm the second member's execution row links to the same session (no second session appears) and the transcript contains both turns. Edit the group to tick Run /compact between members, rerun, and confirm a
/compactcommand runs on the shared session before the second member's prompt (visible in the transcript). Pin the second member to a different workspace and run the group again: that member fails before its prompt with the shared-session workspace/preset error. - Open the board and confirm it lands on the workspace overview list: a column header above one card per workspace with right-aligned counts plus an All tasks card, each workspace a distinct bordered card rather than rows crammed inside one shared box. Click a workspace row's ▸ chevron and confirm its groups and task rows expand inline (each group collapses independently), and that clicking a task row opens the task detail. Pin a task to one workspace and leave another unpinned; click that workspace's row and confirm the column shows the pinned task plus an Unassigned section with the unpinned one, while tasks pinned to other workspaces disappear; press back to return to the overview, then open the All tasks row and confirm the unscoped overview returns.
- Open Workspace default settings from the overview row (or the kanban header ⚙): set a mode, model, endpoints, and permission, tick New tasks start unapproved, and save. Create a task inside that workspace's kanban and confirm the modal has the workspace pre-selected, the execution targets pre-filled, the unapproved toggle on, and the created task shows the Not approved badge and cannot run. Then create a task in that workspace and clear its mode/model/endpoints/permission fields: confirm the blank mode/model options read Workspace default () and the endpoint/permission fields show Workspace default: … hints, and that running it uses the workspace's preset, model, endpoints, and permission (the execution row shows the default endpoint). Clear every default, save, and confirm the workspace's record is gone (new tasks no longer pre-fill and blank fields fall back to the deployment default).
- In the settings card (Settings → Plugins → All Tasks) → Endpoints, raise the idle timeout of the endpoint serving your local provider (e.g.
lm-studioto 600 s) and save; confirm~/.dsh/settings.yamlgainsllm-pi-ai.providers.lm-studio.streamIdleTimeoutMs, then run a task through that model and confirm a long idle gap between generated chunks no longer fails. Editing the endpoint back to the default restores 300 s. - Enable a near-future cron, close all browser pages, and confirm the Host still creates and settles exactly one execution.
- Stop the Host past a cron occurrence, restart it, and confirm the missed occurrence is skipped and
nextRunAtrolls forward from current Host time. - Enable
preventIdleSleep, run a long session, and let the display turn off; after restoring the display, confirm the session continued and the execution settled. - Disable the setting and all schedules, stop DSH, and confirm the helper exits; on macOS,
pmset -g assertionsshould show no display-sleep assertion from this plugin. - On Linux, use
systemd-inhibit --listto confirm that only anidle/blockentry exists; the display should still follow desktop settings, while manual sleep and lid close remain under system policy. - Open the new-task modal, leave the title blank, and type a run prompt: the Generated after you leave the prompt field hint shows, and no request fires while you keep typing. Click out of the prompt field: a Generating a title from the prompt… hint appears, and within a second or two the title field fills with a generated title (findable in the session list as All Tasks · title, a short-lived backend session). Submit and confirm the created task carries that title. Clear the generated title and press Regenerate for a new suggestion; type a manual title over a generated one and confirm it is never overwritten. Turn off Auto-generate task titles in the settings card, create another task from a prompt only, and confirm it gets the prompt's first line as its title without any generation session.
- Create a parallel group with two backlog members plus a third member; open the group editor (⚙), pick the third member as Final step, and save. Confirm the final-step card shows the Final step badge and no run action while the others are unfinished, and the banner shows Final step · waiting. Run the group: only the two parallel members start, and a manual run of the final step is refused (
final step waits for all group members to settle). Move one member to Failed and settle the other: confirm the final step auto-starts (a failed member opens the gate by default). Edit the group to tick Require all other members to succeed, move the members back to To Do, rerun with one member failing, and confirm the final step stays put until that member succeeds. - In any agent session, call
task_createwith a title and prompt (noapproved): confirm the tool returns the new task id and the board shows a todo card with the Agent badge, the Not approved badge, and no way to run it (Run/Rerun refused, cron held). Approve it on the board, then calltask_listandtask_getwith the returned id: confirm the rows reportapproved: true, the run prompt, and the execution targets; confirmtask_geton a bogus id fails with a clear error. Calltask_createagain withapproved: trueand confirm that task is immediately runnable. - On the landing dashboard, confirm the Usage over time section renders between the summary cards and the workspace list with the Cost over time and Tokens over time panels. With no settled runs both panels show the no-usage note. Run a few tasks, then switch the shared dropdown between Hourly / Daily / Weekly and confirm both panels re-bucket (24 / 14 / 8 columns) and the window hints update; hover a column for the per-bucket tooltip. Runs through the official DeepSeek endpoint bill at its hard-coded peak/off-peak rates (confirm the cost panel draws bars and the Est. cost card shows the summed spend); a local endpoint with no per-endpoint pricing set keeps those runs' estimate "—" and the cost panel shows the no-pricing note while the tokens panel still charts — set the endpoint's input/output prices in the Endpoints editor and confirm the cost panel then draws bars.
- Set Settings → Plugins → All Tasks → Keep token usage for the last N hours to 24 and save; run a task, confirm the dashboard Tokens and Est. cost cards count only that run, and their subtitles show a last 24 h prefix (older settled runs drop out of the totals while their execution rows stay in the task history). Set it back to 0 and confirm the totals return to all time. While 24 h is set, confirm the usage charts follow: the daily view shows only the current day plus the partial day holding the cutoff (2 columns), and the panel hints read last 24 h instead of the fixed granularity window.
- Run several tasks to completion and failure, open the kanban, and confirm the Done and Failed column headers each show an archive-icon button (the same glyph the DSH session list shows for "Archive session") counting that column's settled tasks. Click it: the dialog lists the tasks it will hide and offers Also archive their execution sessions in DSH (N sessions), checked by default; unchecking it hides without touching sessions. Confirm with the option on: the tasks leave the columns for the Archive view (counts update), and their sessions disappear from the DSH session list while the transcripts still open from the task detail. Restore one hidden task from the archive view and confirm it returns to its column. Confirm a column whose tasks all sit in another state shows no hide control. Put two done tasks in one task group and one done task ungrouped in the same column: confirm the group banner's archive icon lists only the group's members (the ungrouped card is untouched) while each settled card's own overlaid archive icon hides just that card, and that a group banner in a To Do column (no settled members) shows no hide control.
- Missed occurrences during Host downtime, system sleep, or a long pause are skipped and never queued for catch-up.
- A task that is already running skips its due occurrence and rolls to the next cron match; task runs never overlap or queue.
- DST follows the Host local wall clock: a nonexistent spring-forward minute is skipped, and a repeated fall-back minute is not replayed a second time.
- Power protection prevents only idle system sleep. It deliberately allows display sleep and lock.
- Lid close, manual sleep, hibernation, shutdown, low-battery forced sleep, and enterprise power policy are outside the guarantee.
- The plugin does not schedule wake timers and cannot wake a computer that is already asleep.
- Linux requires systemd-logind and policy permission for the current user to acquire an idle block lock. Containers, WSL, hosts without a system bus, and non-systemd systems may report
unsupportedorerror. Whether a desktop also associates a logind idle lock with display idleness is desktop policy; the plugin does not request a screensaver or display inhibitor. - Keeping enabled schedules armed may increase battery consumption because protection starts before their future trigger time.
- Host execution consumes the same API quota as an ordinary DSH agent session.
The browser half sends one anonymous install heartbeat per UTC day to dsh-market.com: a random localStorage id plus this package's name, nothing else. The server stores only a salted hash of that id, never IP addresses, and exposes aggregate counts only. See the upstream telemetry contract for the full contract.