@linxin666/dsh-client-ui-task-board 0.1.20 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +82 -104
  3. package/README.zh.md +82 -104
  4. package/lib/client.js +574 -581
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2144 -5
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -1
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +7 -4
  9. package/lib/types/client/board/TaskCard.d.ts +5 -2
  10. package/lib/types/client/host-api.d.ts +18 -0
  11. package/lib/types/client/locales.d.ts +10 -0
  12. package/lib/types/core/controller.d.ts +40 -15
  13. package/lib/types/core/schedule.d.ts +4 -2
  14. package/lib/types/core/tasks.d.ts +6 -6
  15. package/lib/types/core/use-cases/task-archive.d.ts +5 -4
  16. package/lib/types/core/use-cases/task-schedule.d.ts +3 -3
  17. package/lib/types/dsh-home.d.ts +16 -0
  18. package/lib/types/host-ledger.d.ts +42 -0
  19. package/lib/types/host-routes.d.ts +24 -0
  20. package/lib/types/host-runner.d.ts +30 -0
  21. package/lib/types/host-service.d.ts +41 -0
  22. package/lib/types/index.d.ts +17 -9
  23. package/lib/types/loopback.d.ts +24 -0
  24. package/lib/types/power-inhibitor.d.ts +73 -0
  25. package/lib/types/protocol.d.ts +72 -0
  26. package/package.json +12 -9
  27. package/src/client/PluginSettingsCard.tsx +10 -0
  28. package/src/client/TaskBoardSettingsCard.tsx +51 -4
  29. package/src/client/board/NewTaskModal.tsx +9 -6
  30. package/src/client/board/TaskBoard.tsx +21 -4
  31. package/src/client/board/TaskCard.tsx +24 -8
  32. package/src/client/board/TaskDetail.tsx +83 -50
  33. package/src/client/host-api.ts +95 -0
  34. package/src/client/index.ts +9 -78
  35. package/src/client/locales.ts +24 -4
  36. package/src/core/controller.ts +205 -24
  37. package/src/core/schedule.ts +20 -3
  38. package/src/core/store.ts +4 -8
  39. package/src/core/tasks.ts +6 -6
  40. package/src/core/use-cases/task-archive.ts +14 -5
  41. package/src/core/use-cases/task-schedule.ts +6 -5
  42. package/src/dsh-home.ts +36 -0
  43. package/src/host-ledger.ts +478 -0
  44. package/src/host-routes.ts +188 -0
  45. package/src/host-runner.ts +134 -0
  46. package/src/host-service.ts +209 -0
  47. package/src/index.ts +54 -11
  48. package/src/loopback.ts +63 -0
  49. package/src/power-inhibitor.ts +321 -0
  50. package/src/protocol.ts +213 -0
  51. package/lib/types/core/scheduler.d.ts +0 -70
  52. package/src/core/scheduler.ts +0 -161
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: ed814f18cf9668624c70d26ef8190d378aaf62f1
6
- README.zh.md: 093c361adb207e3c307b72d1ce5ecabfc62b4af0
5
+ README.md: 9d2e0eea7c24c01a777ac5ae52e73821a7c05ed3
6
+ README.zh.md: 4b217aba13a767f605ac2179ac901c9945631e35
package/README.md CHANGED
@@ -2,135 +2,113 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- A hot-pluggable DeepSeek Harness (DSH) client GUI plugin: it adds a **task board** entry below "新会话" (New session) in the sidebar; clicking it switches the middle column entirely to a multi-column kanban view. Tasks execute for **real** through DSH's own session mechanism (`session.prompt`), and execution status is written back to the card in real time.
5
+ 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.
6
6
 
7
- - No DSH source modification: mounted as a cordis plugin + browser DOM extension (add-on shape identical to `dsh-web-ui/packages/skins/skin-center`).
8
- - Unmounting restores the original state; other managed segments (dsh-skin / skin-center / personal config) are unaffected.
9
- - Task data persists locally; it survives a page refresh and a DSH restart.
7
+ - The browser is an asynchronous view; closing the page does not stop Host scheduling or execution settlement.
8
+ - Every run creates a separate DSH session and applies pinned workspace, agent preset, and permission before sending the task prompt.
9
+ - The display may turn off while optional power protection keeps the computer from entering idle system sleep.
10
10
 
11
11
  ## Features
12
12
 
13
- - **Sidebar entry**: injects a "任务看板" (task board) entry row inside the sidebar column (`[data-pane="sidebar"]` on older shells, `[class*="sidebarCol"]` on the DSH 0.1.0-rc.6 AppFrame layout) below the new-session button (wide rail shows icon + text, collapsed rail shows a bare icon, adapting to DSH skin tokens).
14
- - **Multi-column board**: five columns — 待规划 (to plan) / 待办 (to do) / 进行中 (in progress) / 已完成 (done) / 已失败 (failed); cards show title, description, status, update time, and execution count; the top supports search filter, an archive view (done/failed tasks can be archived off the board and restored anytime, keeping execution history and session transcripts traceable), new task, and back to chat.
15
- - **Task details**: click a card to open details (title/description/execution prompt/execution log) — it does **not** execute on a single click; the details offer "执行 / 重新执行" (Run / Re-run), "删除" (Delete, with confirm), "查看会话" (View session, jumps to the execution transcript), and a manual move to 待规划/待办.
16
- - **Real execution**: on "执行" (Run), the plugin connects a workspace session through the client runtime (`workspaces.connectWorkspace`, reusing a blank session or letting the host create one), names the session after the task title, and drives a real agent via `session.prompt([{ type: 'text', text }], 'queue')`; it then subscribes to that session's snapshot and, once the round really finishes, sets the card to 已完成/已失败 and records the execution result. The execution session appears in the session list and can be opened to view the real transcript.
17
- - **Per-task execution targets**: a task can pin where and how it runs — **workspace** (the execution session lands in that workspace), **mode** (the agent preset the session is composed from, switched through `agentPresets.select` while the session is still blank), and **permission** (a sandbox preset applied through the `/permission <id>` slash command: read-only / workspace-write / danger-full-access). Blank pins fall back to the runtime defaults (recent workspace / deployment preset / session default). A pin that cannot be applied fails the run **before** the prompt is sent, so a task never silently runs under settings it did not ask for.
18
- - **Status write-back**: card status (进行中 → 完成/失败) is driven by the real session state; after a page refresh/restart, leftover running tasks are auto-reconciled against the current session state (reconcile).
19
- - **Scheduled tasks**: the new-task dialog and the details panel can schedule a task — an enable switch + a 5-field cron expression (分 时 日 月 周, supporting `*` / `*/n` / `a-b` / comma lists) + common presets (daily 09:00, every hour, every 10 minutes, Mondays 09:00); enabling computes and persists the "下次运行时间" (next run time), and the card shows a scheduled marker; at the due time it automatically takes the same real-execution path (as manual run), and the execution session remains linkable.
20
- - **System-prompt injection**: the host half (`src/index.ts`) registers a `plugin:task-board` section (order 200) via `SystemPrompt.section`, declaring this plugin's existence, capabilities, and limits to every agent — it is injected when the plugin is in the composition (after mount + DSH restart) and disappears when removed (after unmount + restart), so an agent needs no external docs to know how to work with this board.
21
-
22
- ## Directory structure
23
-
24
- ```
25
- package.json / tsconfig.json / tsdown.config.ts # standalone repo build
26
- build/tsdown.client.ts + build/web/src/platform.ts # client bundle preset copied from the DSH checkout (kept in sync with the running version)
27
- src/index.ts / src/invariant.ts # host half: only injects SystemPrompt section (no other behavior)
28
- src/client/index.ts # apply(ctx): wires runtime services + mounts DOM
29
- src/client/sidebar-entry.ts # sidebar entry injection (self-healing MutationObserver)
30
- src/client/board-mount.tsx # middle-column board mount + show/hide toggle
31
- src/client/board/*.tsx # React board views (columns/cards/details/new/confirm)
32
- src/client/board.module.css # styles (--dsw-* tokens, adapting to theme/skin)
33
- src/core/tasks.ts # task model + state machine (pure functions)
34
- src/core/schedule.ts # cron parsing + next-run time (pure functions)
35
- src/core/scheduler.ts # browser scheduler (ticks every minute to fire due tasks)
36
- src/core/store.ts # persistence (TaskStore interface + localStorage impl)
37
- src/core/execution.ts # real execution service (session connect/prompt/settlement watch)
38
- src/core/controller.ts # controller (ledger state, view state, navigation awareness)
39
- tests/*.spec.ts # automated tests: storage/state transitions/execution trigger/cron/scheduling
40
- scripts/dsh-task-board.js # one-click mount/unmount/status CLI
41
- ```
42
-
43
- ## Why it is wired this way (research conclusions)
44
-
45
- - **No usable add-on slot in the sidebar**: the sidebar shell only declares two single slots, `sidebar.workspaces` / `sidebar.settings`, both already taken by ui-workspace / ui-settings; an external plugin cannot register a new slot (declaring means claiming, and duplicating throws). So the entry goes through the skin-precedent **DOM injection**, self-healed with a MutationObserver (when a React re-render touches the node it re-inserts within the same frame, no flicker).
46
- - **The middle column cannot be replaced through a slot**: the `conversation` slot is single and already taken by ui-conversation. The board view mounts on the center column (`[data-pane="conversation"]` on older shells, `[class*="centerCol"]` on the DSH 0.1.0-rc.6 AppFrame layout) as a tail child node (outside React's ownership), toggled via the `<html data-dsh-taskboard-active>` attribute, keeping the chat subtree below mounted and stateful.
47
- - **Persistence uses browser localStorage**: client plugins run in the browser and DSH has no browser-writable file channel (matching skin-center's research on `cordis.patch.yml`); localStorage is also how DSH's own client snapshot store (`createSnapshotStore` persist) persists.
48
- - **Execution rides the client runtime**: `ctx.sessions.list` subscribes to session state (`running` / `byId`), `ctx.workspaces.connectWorkspace()` creates/reuses a session, `session.prompt()` drives a real agent, and `ctx.sessions.open()` jumps to the transcript.
49
- - **Execution targets ride the same runtime faces**: the workspace pin passes the task's id to `workspaces.connectWorkspace()` (validated against the workspace list first, so a stale pin fails locally); the mode pin recomposes the blank execution session via `api.agentPresets.select` — only legal before the first turn, so it runs before `session.prompt`, and `sessions.noteAgentPreset` keeps the list label current; the permission pin admits a `/permission <id>` slash command through `session.command` — the same mechanism the shell's own permission picker uses. A rejected admission or a line no command claims fails the run before the prompt.
50
- - **Background settlement relies on list reconciliation**: an unopened session has no chat-snapshot window (cold), so settlement keys off the session list — every list change reconciles running tasks; result judgment takes, in order, "missing from list → cancelled / still running → wait / chat snapshot visible → by lastAgentError / tail of raw history → a turn-error node proves failure / otherwise success", and reconciliation is idempotent.
51
- - **Scheduled tasks run in the browser scheduler**: the plugin is pure-client (no server channel), so "run at the due time" is done by the in-tab scheduler — a tick every minute, with an immediate catch-up tick when the page returns from the background; before firing it first moves "next run" forward to the next cron match so the same tick never fires twice; it does not fire early in page load (before the session-list baseline is ready), avoiding mis-execution. Every tick first re-reads the persisted ledger, so a task deleted in another tab can never be fired from a stale in-memory copy. Limitation: the tab must stay open (schedules missed while closed are "miss = skip", and only already-deferred due tasks are caught up on the next open); a task that is 进行中 (in progress) skips the current due time and waits for the next cron match.
52
- - **Same-origin tabs share one ledger**: additions, edits, and deletions in any tab propagate to the others through storage events (`LocalStorageTaskStore.subscribeExternal`), so a task deleted in one tab can never keep firing from another tab's stale in-memory copy — nor be written back by that copy's later persistence (schedule roll-forward, execution settlement).
13
+ - **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.
14
+ - **Host-authoritative ledger**: tasks, schedules, and execution records live in `$DSH_HOME/task-board/ledger-v2.json`; browser actions become confirmed Host transactions.
15
+ - **Real execution**: manual and scheduled runs use the same Host runner, create a fresh session, rename it, apply the agent preset and `/permission <id>`, then queue the task prompt.
16
+ - **Fail-closed pins**: a missing workspace, missing or broken preset, or rejected permission command fails before the task prompt is sent.
17
+ - **Host scheduler**: 5-field cron supports `*`, `*/n`, ranges, comma lists, Sunday `0/7`, and standard day-of-month/day-of-week OR semantics in the Host local time zone.
18
+ - **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.
19
+ - **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.
20
+ - **Optional idle-sleep protection**: off by default; when enabled it covers every running DSH session, enabled non-archived task-board schedules, and unknown session state.
21
+ - **System-prompt injection**: the Host registers a `plugin:task-board` section (order 200) through `SystemPrompt.section`, and the task-board settings can disable the announcement without disabling the board.
22
+
23
+ ## Architecture and protocol
24
+
25
+ - `src/index.ts` mounts the Host service through the official `@deepseek-ai/dsh-host-apiproxy` and `@deepseek-ai/dsh-host-webserver` SDKs.
26
+ - `src/host-ledger.ts` serializes actions and persists `{ schemaVersion: 2, revision, tasks, scheduler, recentRequests }` through a temporary file plus atomic rename.
27
+ - `src/host-service.ts` owns cron ticks, missed-trigger skipping, runner launch, restart reconciliation, and power reasons.
28
+ - `src/client/host-api.ts` imports legacy browser data once, submits idempotent actions, and treats Host snapshots as the only confirmed UI state.
29
+ - Same-origin endpoints are `GET /api/task-board/state`, `GET /api/task-board/events`, and `POST /api/task-board/action`.
30
+ - 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.
53
31
 
54
32
  ## Install
55
33
 
56
- Install the family aggregate package `@linxin666/dsh-web-ui-all` (all plugins and skins in one) or this plugin alone:
34
+ Install the aggregate package or this package alone, then restart `dsh web`:
57
35
 
58
36
  ```sh
59
- ### 从 npm 安装(推荐)
60
37
  dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
38
+ ```
39
+
40
+ For local development:
61
41
 
62
- ### 从仓库安装(开发调试)
42
+ ```sh
63
43
  git clone https://github.com/zhu1090093659/dsh-web-ui.git
64
44
  cd dsh-web-ui
65
- pnpm install && pnpm -r build
45
+ pnpm install
46
+ pnpm build
66
47
  dsh plugin --profile web add link:$(pwd)/packages/dsh-task-board
67
-
68
48
  ```
69
49
 
70
- After installing, **restart `dsh web`** — a "任务看板" (task board) entry appears below "新会话" (New session) in the sidebar; a page refresh is not enough, the process must restart.
50
+ ## Configuration
71
51
 
72
- ## Build
52
+ | Key | Default | Behavior |
53
+ | --- | --- | --- |
54
+ | `enabled` | `true` | Enables the Host service and browser board. |
55
+ | `announceToAgent` | `true` | Adds the task-board guidance section to agent system prompts. |
56
+ | `preventIdleSleep` | `false` | Holds one system idle-sleep assertion while any DSH session runs, any schedule is enabled, or session state is unknown. |
57
+ | `trustedProxyHosts` | `[]` | Canonical `host[:port]` authorities accepted only through the authenticated loopback reverse-proxy path. |
58
+ | `proxyTokenEnv` | `DSH_TASK_BOARD_PROXY_TOKEN` | Environment variable containing the reverse-proxy token; the token itself is never stored in plugin config. |
73
59
 
74
- Prerequisites: Node ≥ 20 with the official NPM SDK reachable (configure the `NPM_TOKEN` env var + project `.npmrc` if still using private-scope auth; see the repo `docs/plugins.md`). Types and runtime APIs all come from the official NPM SDK (`@deepseek-ai/*` devDependencies); **no DSH source checkout is required**.
60
+ 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-Task-Board-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.
75
61
 
76
- ```sh
77
- cd ~/code/dsh-web-ui/packages/dsh-task-board
78
- pnpm install # first time (run pnpm install at the workspace root)
79
- pnpm run build # produces lib/index.js + lib/client.js (tsdown + shared/tsdown.client.ts preset)
80
- pnpm run typecheck # type check (SDK package types from node_modules)
81
- pnpm test # vitest: storage read/write / state transitions / execution trigger
82
- ```
62
+ 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`.
83
63
 
84
- ## Mount / Unmount
64
+ ## Data storage and migration
85
65
 
86
- This plugin uses the official profile-bundle shape (package.json declares `dsh.bundle.patch` + `dsh.client`, see `cordis.patch.yml`). Mounting = registering the dependency and bundle rows in the web profile manifest (`~/.dsh/profiles/web/package.json`) and installing:
66
+ - The v2 ledger is `$DSH_HOME/task-board/ledger-v2.json`. New POSIX files use mode `0600`; Windows inherits the user directory ACL.
67
+ - 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.
68
+ - On the first upgraded page load for an origin, `dsh.taskBoard.v1` 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.
69
+ - 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.
70
+ - The import marker `dsh.taskBoard.v2.hostImported` stores 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.
71
+ - One Host process owns a task-board ledger directory at a time through `$DSH_HOME/task-board/ledger-v2.lock`; a second Host using the same DSH home fails closed instead of concurrently writing the ledger.
87
72
 
88
- ```sh
89
- # Mount (registers dependencies + dsh.profile.bundles, pnpm install; takes effect after restarting the GUI)
90
- node scripts/dsh-task-board.js mount
73
+ ## Security model
91
74
 
92
- # View status
93
- node scripts/dsh-task-board.js status
75
+ - 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.
76
+ - All mutation payloads use a strict, versioned discriminated union; schedule-owned timestamps and execution outcomes cannot be written by the browser.
77
+ - Workspace, preset, permission, cron, task status, and imported records are validated again on the Host.
78
+ - 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.
79
+ - 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.
94
80
 
95
- # Unmount (removes the registered rows; restores the original GUI after restart; task data is kept)
96
- node scripts/dsh-task-board.js unmount
97
- ```
81
+ ## Build and test
98
82
 
99
- The rows registered in the profile manifest:
83
+ Node 20 or newer and the official NPM SDK packages are required; no DSH source checkout is used.
100
84
 
101
- ```json
102
- {
103
- "dependencies": { "@linxin666/dsh-client-ui-task-board": "link:/Users/zcl/code/dsh-web-ui/packages/dsh-task-board" },
104
- "dsh": { "profile": { "bundles": [ "...", "@linxin666/dsh-client-ui-task-board" ] } }
105
- }
85
+ ```sh
86
+ pnpm --filter @linxin666/dsh-client-ui-task-board typecheck
87
+ pnpm --filter @linxin666/dsh-client-ui-task-board test
88
+ pnpm --filter @linxin666/dsh-client-ui-task-board build
106
89
  ```
107
90
 
108
- > Note: the profile layer (bundle rows, `dsh.client` metadata) is read when the dsh web process starts, so a **restart of the dsh web GUI** is required after mount/unmount (a page refresh is not enough).
109
-
110
- ## Data storage location
111
-
112
- - The task ledger lives in browser localStorage under the key `dsh.taskBoard.v1` (origin `http://127.0.0.1:<dsh web port>`; same origin persists across refresh/restart).
113
- - Data is retained after unmount; to clear it, run `localStorage.removeItem("dsh.taskBoard.v1")` in the browser console.
114
- - The storage layer is the `TaskStore` interface (`src/core/store.ts`); it can later be swapped for IndexedDB or a host file channel without touching the upper logic.
115
-
116
- ## Manual verification steps
117
-
118
- 1. `npm run build` → `node scripts/dsh-task-board.js mount` → refresh `http://127.0.0.1:3080`.
119
- 2. A "任务看板" (task board) entry row appears below "新会话" in the sidebar; click it → the middle column switches to the five-column board.
120
- 3. "+ 新建任务" (New task) with title/description/Prompt → the card appears in 待办 (to do). The dialog also offers 工作区/模式/权限 (workspace / mode / permission) pins — leave them blank for runtime defaults.
121
- 4. Pin a workspace/mode/permission on a task (in the dialog or the task detail) → run it → the execution session appears under the pinned workspace, its list row shows the pinned preset, and the session's permission selector shows the pinned permission.
122
- 5. Click the card → details show content and Prompt; click "执行" (Run) → the card becomes 进行中 (in progress) (a session named after the task title appears in the session list); after the agent finishes the card lands in 已完成 (done) or 已失败 (failed), the detail execution log has a result and time, and "查看会话" (View session) jumps to the real transcript.
123
- 6. Scheduled task: details → tick "定时运行" (Scheduled run) to enable, pick the preset "每 10 分钟" (every 10 minutes, cron `*/10 * * * *`); a scheduled marker appears on the card; wait for the next whole 10-minute mark, watch the card automatically enter 进行中 (in progress) and eventually complete, with "上次触发" (last trigger) showing a time and a new execution-log row (the session is linkable).
124
- 7. Refresh the page / restart DSH → tasks remain; unmount the plugin → the GUI restores to its original state.
125
-
126
- ## Acceptance checklist
127
-
128
- - After mount, a "任务看板" (task board) entry appears in the sidebar; clicking toggles the board, and clicking a session item returns to the chat view
129
- - New task (title + description/Prompt); tasks remain after refresh/restart (localStorage persistence)
130
- - Click a card to open details (content + execution log); the details have "执行" (Run) and "删除" (Delete) buttons
131
- - Execution really starts a session (its transcript is visible in the session list); card status follows the real execution progress; the details can jump to the execution session
132
- - Delete has a confirm step, and the local store is synced-removed after deletion
133
- - Scheduled tasks: cron config/preset/validation, next-run time, auto real execution at the due time, status write-back, scheduled card marker, scheduling resumes after refresh (browser-side scheduling, the tab must stay open)
134
- - Per-task execution targets: workspace/mode/permission pins persist across refresh, drive the execution session, and an un-appliable pin (missing workspace, locked preset, unknown permission command) fails the run with the reason visible in the execution log
135
- - One-click mount/unmount; after unmount the GUI restores and other managed segments are unaffected
136
- - README + automated tests covering storage read/write, state transitions, execution trigger, cron parsing, and the scheduler
91
+ Set `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.
92
+
93
+ ## Manual verification
94
+
95
+ 1. Mount the package, restart `dsh web`, open the task board, and confirm the Host time zone and power status are visible.
96
+ 2. Create and edit a task; refresh or open a second same-origin tab and confirm both show the same Host revision.
97
+ 3. Run a task with pinned workspace, preset, and permission; confirm a new session appears and the task settles from its `turn/end` history.
98
+ 4. Enable a near-future cron, close all browser pages, and confirm the Host still creates and settles exactly one execution.
99
+ 5. Stop the Host past a cron occurrence, restart it, and confirm the missed occurrence is skipped and `nextRunAt` rolls forward from current Host time.
100
+ 6. Enable `preventIdleSleep`, run a long session, and let the display turn off; after restoring the display, confirm the session continued and the execution settled.
101
+ 7. Disable the setting and all schedules, stop DSH, and confirm the helper exits; on macOS, `pmset -g assertions` should show no display-sleep assertion from this plugin.
102
+ 8. On Linux, use `systemd-inhibit --list` to confirm that only an `idle`/`block` entry exists; the display should still follow desktop settings, while manual sleep and lid close remain under system policy.
103
+
104
+ ## Known limitations
105
+
106
+ - Missed occurrences during Host downtime, system sleep, or a long pause are skipped and never queued for catch-up.
107
+ - A task that is already running skips its due occurrence and rolls to the next cron match; task runs never overlap or queue.
108
+ - 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.
109
+ - Power protection prevents only idle system sleep. It deliberately allows display sleep and lock.
110
+ - Lid close, manual sleep, hibernation, shutdown, low-battery forced sleep, and enterprise power policy are outside the guarantee.
111
+ - The plugin does not schedule wake timers and cannot wake a computer that is already asleep.
112
+ - 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 `unsupported` or `error`. 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.
113
+ - Keeping enabled schedules armed may increase battery consumption because protection starts before their future trigger time.
114
+ - Host execution consumes the same API quota as an ordinary DSH agent session.
package/README.zh.md CHANGED
@@ -2,135 +2,113 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 一个可热插拔的 DeepSeek Harness (DSH) 客户端 GUI 插件:在侧边栏「新会话」下方增加 **任务看板**入口,点击后中间列整体切换为多列看板视图;任务以 DSH 自身的会话机制 **真实执行**(`session.prompt`),执行状态实时回写卡片。
5
+ 一个可热插拔的 DeepSeek Harness (DSH) Web GUI 插件,提供 Host 权威任务账本、真实 DSH 会话执行、Host cron 调度和可选的跨平台空闲睡眠保护。插件只通过 `cordis.patch.yml` 与 profile 机制挂载,不修改 DSH 源码。
6
6
 
7
- - 不修改 DSH 源码:以 cordis 插件 + 浏览器 DOM 扩展挂载(外挂形态与 `dsh-web-ui/packages/skins/skin-center` 一致)。
8
- - 卸载即恢复原状,其它 managed 段(dsh-skin / skin-center / 个人配置)互不干扰。
9
- - 任务数据本地持久化,刷新页面、重启 DSH 均不丢失。
7
+ - 浏览器只是异步视图;关闭页面不会停止 Host 调度或执行结算。
8
+ - 每次运行创建独立 DSH 会话,并在发送任务 Prompt 前应用钉住的工作区、agent 预设与权限。
9
+ - 可选电源保护允许显示器熄灭,同时阻止整机因空闲进入系统睡眠。
10
10
 
11
11
  ## 功能
12
12
 
13
- - **侧边栏入口**:侧边栏列(旧版 `[data-pane="sidebar"]`,DSH 0.1.0-rc.6 AppFrame 布局为 `[class*="sidebarCol"]`)内、新会话按钮下方注入「任务看板」入口行(宽栏显示图标+文字,折叠 rail 显示纯图标,随 DSH 皮肤 token 自适应)。
14
- - **多列看板**:待规划 / 待办 / 进行中 / 已完成 / 已失败 五列;卡片显示标题、描述、状态、更新时间、执行次数;顶部支持搜索过滤、归档视图(已完成/已失败任务可归档,归档后移出看板,可随时恢复,执行记录与会话 transcript 保留可追溯)、新建任务、返回会话。
15
- - **任务详情**:点卡片打开详情(标题/描述/执行 Prompt/执行记录),**不会**一点就执行;详情内提供「执行 / 重新执行」「删除(带确认)」「查看会话(跳转到执行 transcript)」以及手动移到待规划/待办。
16
- - **真实执行**:点「执行」后,插件通过客户端 runtime 连接工作区会话(`workspaces.connectWorkspace`,空白会话复用或 host 新建),把任务标题设为会话名,以任务 Prompt 调用 `session.prompt([{ type: 'text', text }], 'queue')` 驱动真实 agent;随后订阅该会话快照,轮次真实结束后把卡片置为 已完成/已失败 并记录执行结果。执行会话会出现在会话列表,可点进对话查看真实 transcript。
17
- - **任务级执行目标**:任务可钉住「在哪跑、怎么跑」——**工作区**(执行会话落在指定工作区)、**模式**(会话以指定 agent 预设组合,趁会话仍为空白时走 `agentPresets.select` 切换)、**权限**(经 `/permission <id>` 斜杠命令应用的沙箱预设:read-only / workspace-write / danger-full-access)。留空即回退运行时默认(最近工作区 / 部署预设 / 会话默认)。钉不住的目标会在发 Prompt **之前**失败,任务绝不会悄悄按没要求过的设置运行。
18
- - **状态回写**:卡片状态(进行中 → 完成/失败)由真实会话状态驱动;刷新页面/重启后,遗留的 running 任务会按会话现状自动对账(reconcile)。
19
- - **定时任务**:新建任务与详情面板均可配置定时执行——启用开关 + 5 段 cron 表达式(分 时 日 月 周,支持 `*` / `*/n` / `a-b` / 逗号列表)+ 常用预设(每天 09:00、每小时、每 10 分钟、每周一 09:00);启用即计算并持久化「下次运行时间」,卡片显示定时标识;到点自动走真实执行链路(同手动执行),执行会话照常可跳转。
20
- - **系统提示词注入**:host 半边(`src/index.ts`)通过 `SystemPrompt.section` 注册 `plugin:task-board` 段(order 200),向每个 agent 声明本插件存在、能力与限制——插件在组合中(mount 后重启 DSH)即注入,移出组合(unmount 后重启)即消失,agent 无需任何外部文档就能知道如何与本看板协作。
21
-
22
- ## 目录结构
23
-
24
- ```
25
- package.json / tsconfig.json / tsdown.config.ts # 独立仓库构建
26
- build/tsdown.client.ts + build/web/src/platform.ts # 从 DSH checkout 复制的 client bundle 预设(与运行版本保持同步)
27
- src/index.ts / src/invariant.ts # host 半边:仅注入 SystemPrompt section(其余无行为)
28
- src/client/index.ts # apply(ctx):接线 runtime 服务 + 挂载 DOM
29
- src/client/sidebar-entry.ts # 侧边栏入口注入(自愈式 MutationObserver)
30
- src/client/board-mount.tsx # 中间列看板挂载 + 显隐切换
31
- src/client/board/*.tsx # React 看板视图(列/卡片/详情/新建/确认)
32
- src/client/board.module.css # 样式(--dsw-* token,随主题/皮肤自适应)
33
- src/core/tasks.ts # 任务模型 + 状态机(纯函数)
34
- src/core/schedule.ts # cron 解析 + 下次运行时刻(纯函数)
35
- src/core/scheduler.ts # 浏览器调度器(每分钟 tick 触发到期任务)
36
- src/core/store.ts # 持久化(TaskStore 接口 + localStorage 实现)
37
- src/core/execution.ts # 真实执行服务(会话连接/prompt/结算观察)
38
- src/core/controller.ts # 控制器(台账状态、视图状态、导航感知)
39
- tests/*.spec.ts # 存储/状态流转/执行触发/cron/调度 自动化测试
40
- scripts/dsh-task-board.js # 一键挂载/卸载/状态 CLI
41
- ```
42
-
43
- ## 为什么这样接(调研结论)
44
-
45
- - **侧边栏没有可用的外挂槽位**:侧边栏壳只声明 `sidebar.workspaces` / `sidebar.settings` 两个 single 槽位,且已被 ui-workspace / ui-settings 占用;外部插件无法注册新槽位(声明即占有,重复声明抛错)。因此入口行走 skin 先例的 **DOM 注入**,并用 MutationObserver 自愈(React 重渲染波及该节点时同帧内重新插入,无闪烁)。
46
- - **中间列无法通过槽位替换**:`conversation` 槽位是 single 且已被 ui-conversation 占用。看板视图以 DOM 方式挂在中间列(旧版 `[data-pane="conversation"]`,DSH 0.1.0-rc.6 AppFrame 布局为 `[class*="centerCol"]`;挂载选择器两者都保留)内(React 不管的尾部子节点),通过 `<html data-dsh-taskboard-active>` 属性切换显隐,底下的对话子树保持挂载有状态。
47
- - **持久化用浏览器 localStorage**:客户端插件跑在浏览器里,DSH 没有浏览器可写的文件通道(与 skin-center 对 `cordis.patch.yml` 的调研结论一致);localStorage 也是 DSH 客户端自身快照存储(`createSnapshotStore` persist)的持久化方式。
48
- - **执行走客户端 runtime**:`ctx.sessions.list` 订阅会话状态(`running` / `byId`),`ctx.workspaces.connectWorkspace()` 创建/复用会话,`session.prompt()` 真实驱动 agent,`ctx.sessions.open()` 跳转 transcript。
49
- - **执行目标走同样的 runtime 脸**:工作区钉子把任务指定的 id 传给 `workspaces.connectWorkspace()`(先对照工作区列表校验,失效的钉子就地失败);模式钉子通过 `api.agentPresets.select` 重组仍为空白状态的执行会话——只有首轮之前合法,所以排在 `session.prompt` 之前执行,`sessions.noteAgentPreset` 让会话列表标签即时更新;权限钉子经 `session.command` 提交 `/permission <id>` 斜杠命令——与壳自带权限选择器同一机制。提交被拒或没有命令认领该行时,在发 Prompt 前失败。
50
- - **后台结算靠列表对账**:未打开的会话没有对话快照窗口(cold),所以执行结算以会话列表为准——每次列表变化都对账 running 任务;结果判定依次取「列表缺失→已取消 / 仍在跑→等待 / 对话快照可见→按 lastAgentError / 原始历史尾部→turn-error 节点证明失败 / 否则按成功」,对账幂等。
51
- - **定时任务在浏览器端调度**:插件是纯客户端(无服务端通道),所以「到点执行」由标签页内的调度器完成——每分钟 tick 一次,页面从后台恢复可见时立即补 tick;到点触发前先把「下次运行」顺延到下一个 cron 匹配点再执行,同一 tick 不会重复触发;页面加载早期(会话列表基线未就绪)不触发,避免误执行;每次 tick 先重读持久化账本,在别的标签页删除的任务绝不会从陈旧内存副本被触发。限制:需要标签页保持打开(关闭期间错过的调度按「错过即跳过」处理,下次打开时只补跑已顺延的到期任务);任务处于「进行中」时到点跳过本次,等下一个 cron 匹配点。
52
- - **多标签页同源共享同一份台账**:任一标签页的增删改通过 storage 事件同步到其他标签页(`LocalStorageTaskStore.subscribeExternal`),删除的任务不会在其他标签页的内存副本里残留触发,也不会被其他标签页的后续持久化写回复活。
13
+ - **任务看板 UI**:新会话按钮下方的侧边栏入口在宽栏显示图标和文字、在折叠 rail 显示图标;看板提供五列布局、搜索、任务详情、归档/恢复、执行历史和执行会话跳转。归档任务除恢复、删除和查看 transcript 外保持只读,恢复前不能手动或定时执行。
14
+ - **Host 权威账本**:任务、计划和执行记录存于 `$DSH_HOME/task-board/ledger-v2.json`;浏览器动作只有经 Host 确认后才成为 UI 状态。
15
+ - **真实执行**:手动运行和定时运行共用 Host runner,新建独立会话、重命名、应用 agent 预设和 `/permission <id>`,再以 queue 模式发送任务 Prompt。
16
+ - **钉子失败即关闭**:工作区缺失、预设缺失或损坏、权限命令被拒绝时,任务 Prompt 不会发送。
17
+ - **Host 调度器**:5 段 cron 支持 `*`、`*/n`、范围、逗号列表、周日 `0/7` 和标准的日期/星期 OR 语义,时间基准为 Host 本地时区。
18
+ - **确定性恢复**:已有 session id 的 running execution 在重启后继续观察;没有 session id 的启动中断会取消且不会重发。
19
+ - **实时同步**:变更返回完整 revision snapshot;SSE 只提示 revision、scheduler 与 power 变化,重连和页面恢复可见时重新拉完整 snapshot。
20
+ - **可选空闲睡眠保护**:默认关闭;开启后覆盖全部运行中的 DSH 会话、已启用且未归档的任务计划和未知会话状态。
21
+ - **系统提示词注入**:Host 通过 `SystemPrompt.section` 注册 order 200 的 `plugin:task-board` 段;任务看板设置可单独关闭声明而不关闭看板。
22
+
23
+ ## 架构与协议
24
+
25
+ - `src/index.ts` 通过官方 `@deepseek-ai/dsh-host-apiproxy` 与 `@deepseek-ai/dsh-host-webserver` SDK 挂载 Host 服务。
26
+ - `src/host-ledger.ts` 串行动作,并用临时文件加原子 rename 持久化 `{ schemaVersion: 2, revision, tasks, scheduler, recentRequests }`。
27
+ - `src/host-service.ts` 负责 cron tick、错过触发跳过、runner 启动、重启对账和电源保护理由。
28
+ - `src/client/host-api.ts` 单次导入旧浏览器数据、提交幂等动作,并把 Host snapshot 当作唯一已确认 UI 状态。
29
+ - 同源接口为 `GET /api/task-board/state`、`GET /api/task-board/events` 和 `POST /api/task-board/action`。
30
+ - 所有接口都要求浏览器同源标记。直接访问只允许 DSH loopback origin;同机认证反向代理必须使用显式 Host 白名单和服务端注入 token。POST 还必须为 JSON。普通动作上限 64 KiB,导入上限 2 MiB。action 联合中没有命令、可执行路径、shell 文本或任意参数字段。
53
31
 
54
32
  ## 安装
55
33
 
56
- 推荐直接安装全家桶聚合包 `@linxin666/dsh-web-ui-all`(一个包装齐全部功能插件与皮肤),或单独安装本插件:
34
+ 安装聚合包或单独安装本包,然后重启 `dsh web`:
57
35
 
58
36
  ```sh
59
- ### 从 npm 安装(推荐)
60
37
  dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
38
+ ```
39
+
40
+ 本地开发安装:
61
41
 
62
- ### 从仓库安装(开发调试)
42
+ ```sh
63
43
  git clone https://github.com/zhu1090093659/dsh-web-ui.git
64
44
  cd dsh-web-ui
65
- pnpm install && pnpm -r build
45
+ pnpm install
46
+ pnpm build
66
47
  dsh plugin --profile web add link:$(pwd)/packages/dsh-task-board
67
-
68
48
  ```
69
49
 
70
- 安装后**重启 `dsh web`**,侧边栏「新会话」下方出现「任务看板」入口即生效;页面刷新不够,需重启进程。
50
+ ## 配置
71
51
 
72
- ## 构建
52
+ | 键 | 默认值 | 行为 |
53
+ | --- | --- | --- |
54
+ | `enabled` | `true` | 启用 Host 服务与浏览器看板。 |
55
+ | `announceToAgent` | `true` | 向 agent 系统提示加入任务看板说明。 |
56
+ | `preventIdleSleep` | `false` | 存在运行中的 DSH 会话、已启用计划或未知会话状态时,持有一个系统空闲睡眠断言。 |
57
+ | `trustedProxyHosts` | `[]` | 仅通过已认证 loopback 反向代理路径接受的规范 `host[:port]` authority 白名单。 |
58
+ | `proxyTokenEnv` | `DSH_TASK_BOARD_PROXY_TOKEN` | 保存反向代理 token 的环境变量名;token 本身不会写入插件配置。 |
73
59
 
74
- 前置:Node ≥ 20,官方 NPM SDK 可访问(若仍使用私有 scope 认证则配置 `NPM_TOKEN` 环境变量 + 项目 `.npmrc`,见仓库 `docs/plugins.md`)。类型与运行时 API 全部来自官方 NPM SDK(`@deepseek-ai/*` devDependencies),**无需任何 DSH 源码 checkout**。
60
+ 浏览器直接访问仍限制为 DSH loopback origin。若使用同机认证反向代理,应让 DSH Web 绑定 loopback,配置 `trustedProxyHosts`,在 `proxyTokenEnv` 指定的环境变量中放置高熵 token,并让代理在完成认证后替换(不能透传客户端提供的)`X-Dsh-Task-Board-Proxy-Token`。代理 Host 必须在白名单内,浏览器 `Origin` 必须与其 authority 相同。修改这些 composition 级代理设置后需重启 Host。
75
61
 
76
- ```sh
77
- cd ~/code/dsh-web-ui/packages/dsh-task-board
78
- pnpm install # 首次(workspace 根执行 pnpm install)
79
- pnpm run build # 产出 lib/index.js + lib/client.js(tsdown + shared/tsdown.client.ts 预设)
80
- pnpm run typecheck # 类型检查(node_modules 的 SDK 包类型)
81
- pnpm test # vitest:存储读写 / 状态流转 / 执行触发
82
- ```
62
+ macOS 后端启动 `/usr/bin/caffeinate -i -w <host-pid>`,绝不请求 `-d`。Windows 后端从 `SystemRoot` 启动绝对路径的 Windows PowerShell,固定 helper 只请求 `ES_CONTINUOUS | ES_SYSTEM_REQUIRED`;不请求 `ES_DISPLAY_REQUIRED`,不修改电源计划,也不需要管理员权限。Linux 后端只从 `/usr/bin/systemd-inhibit` 或 `/bin/systemd-inhibit` 启动 systemd-logind `idle` block inhibitor,不请求 `sleep`、`handle-lid-switch` 或显示器/屏保 inhibitor;没有 systemd-logind 时显示 `unsupported` 或可见错误,不启动桌面环境专用替代命令。其他平台报告 `unsupported`。
83
63
 
84
- ## 挂载 / 卸载
64
+ ## 数据存储与迁移
85
65
 
86
- 本插件采用官方 profile-bundle 形态(package.json 声明 `dsh.bundle.patch` + `dsh.client`,见 `cordis.patch.yml`)。挂载 = 在 web profile 清单(`~/.dsh/profiles/web/package.json`)注册依赖与 bundle 行并安装:
66
+ - v2 账本位于 `$DSH_HOME/task-board/ledger-v2.json`。POSIX 新文件权限为 `0600`;Windows 继承用户目录 ACL。
67
+ - 损坏的 v2 文件会移动到防碰撞的 `ledger-v2.json.corrupt-*` 名称,Host 以空账本和可见 scheduler 错误启动,不覆盖损坏字节。
68
+ - 每个 origin 首次加载新版页面时,按稳定 source id 和 request id 导入 `dsh.taskBoard.v1`。任务按 id 合并,浏览器端严格较新的顶层字段优先,时间戳相同时保留 Host 字段,执行记录按 execution id 合并。
69
+ - 最近 256 个 request id 与动作的 SHA-256 指纹会随账本持久化,因此 Host 重启后的变更重试仍保持幂等,且不会复制完整动作载荷。
70
+ - 只有导入成功并经 Host 确认后,`dsh.taskBoard.v2.hostImported` 才保存当前 Host 账本 generation;新建或损坏恢复出的新 generation 会再次接收保留的 v1 数据。v1 localStorage 原值保持不变,作为只读回退备份。
71
+ - 同一时间只有一个 Host 进程能通过 `$DSH_HOME/task-board/ledger-v2.lock` 持有任务看板账本目录;第二个使用同一 DSH home 的 Host 会失败关闭,不并发写账本。
87
72
 
88
- ```sh
89
- # 挂载(dependencies + dsh.profile.bundles 注册,pnpm install;重启 GUI 后生效)
90
- node scripts/dsh-task-board.js mount
73
+ ## 安全模型
91
74
 
92
- # 查看状态
93
- node scripts/dsh-task-board.js status
75
+ - 插件仍处在 DSH Web 既有部署与网络边界内,不返回宽松 CORS 头。state、action 与 SSE 共用同一访问栅栏;裸本地命令行请求不会被当作浏览器请求接受。
76
+ - 所有变更载荷使用严格、版本化的判别联合;浏览器不能写入 scheduler 独占时间戳或 execution 结果。
77
+ - 工作区、预设、权限、cron、任务状态和导入记录都会在 Host 再校验。
78
+ - 任务 Prompt 是发给 DSH agent 会话的数据。协议不接受 shell 命令、PowerShell 正文、可执行路径或可配置 helper 参数。
79
+ - 电源 helper 使用固定可执行路径、固定参数、`shell: false`,失败后按 1、2、5、10、30 秒有界退避。Linux helper 通过 Host stdin 生命周期退出,使 systemd inhibitor 随 Host 异常退出自动释放。
94
80
 
95
- # 卸载(移除注册行;重启 GUI 后恢复原状;任务数据保留)
96
- node scripts/dsh-task-board.js unmount
97
- ```
81
+ ## 构建与测试
98
82
 
99
- profile 清单中注册的行:
83
+ 需要 Node 20 或更高版本及官方 NPM SDK 包;不使用 DSH 源码 checkout。
100
84
 
101
- ```json
102
- {
103
- "dependencies": { "@linxin666/dsh-client-ui-task-board": "link:/Users/zcl/code/dsh-web-ui/packages/dsh-task-board" },
104
- "dsh": { "profile": { "bundles": [ "...", "@linxin666/dsh-client-ui-task-board" ] } }
105
- }
85
+ ```sh
86
+ pnpm --filter @linxin666/dsh-client-ui-task-board typecheck
87
+ pnpm --filter @linxin666/dsh-client-ui-task-board test
88
+ pnpm --filter @linxin666/dsh-client-ui-task-board build
106
89
  ```
107
90
 
108
- > 注意:profile 层(bundle 行、`dsh.client` 元数据)在 dsh web 进程启动时读取,挂载/卸载后需要**重启 dsh web GUI** 才生效(页面刷新不够)。
109
-
110
- ## 数据存储位置
111
-
112
- - 任务台账存于浏览器 localStorage,键 `dsh.taskBoard.v1`(来源为 `http://127.0.0.1:<dsh web 端口>`,同一来源跨刷新/重启持久)。
113
- - 卸载插件后数据保留;如需清除,浏览器控制台执行 `localStorage.removeItem("dsh.taskBoard.v1")`。
114
- - 存储层是 `TaskStore` 接口(`src/core/store.ts`),后续可换成 IndexedDB 或 host 文件通道而不动上层逻辑。
115
-
116
- ## 手动验证步骤
117
-
118
- 1. `npm run build` → `node scripts/dsh-task-board.js mount` → 刷新 `http://127.0.0.1:3080`。
119
- 2. 侧边栏「新会话」下方出现「任务看板」入口行;点击 → 中间列切换为五列看板。
120
- 3. 「+ 新建任务」填标题/描述/Prompt → 卡片出现在「待办」;对话框同时提供 工作区/模式/权限 三项钉子,留空即运行时默认。
121
- 4. 在对话框或详情里给任务钉上工作区/模式/权限 → 执行 → 执行会话落在指定工作区下,列表行显示指定预设,会话的权限选择器显示指定权限。
122
- 5. 点卡片 → 详情可见内容与 Prompt;点「执行」→ 卡片变「进行中」(会话列表出现以任务标题命名的会话);agent 跑完后卡片落「已完成」或「已失败」,详情执行记录有结果与时间,可「查看会话」跳转到真实 transcript。
123
- 6. 定时任务:详情 →「定时运行」勾选启用,选预设「每 10 分钟」(cron `*/10 * * * *`),卡片出现定时标识;等待下一个整 10 分钟点,观察卡片自动进入「进行中」并最终完成,详情「上次触发」出现时间、执行记录新增一条(会话可跳转)。
124
- 7. 刷新页面/重启 DSH → 任务仍在;卸载插件 → GUI 恢复原状。
125
-
126
- ## 验收对照
127
-
128
- - 挂载后侧边栏出现「任务看板」入口;点击切换看板,点会话项返回对话视图
129
- - 新建任务(标题+描述/Prompt);刷新/重启后任务仍在(localStorage 持久化)
130
- - 点卡片开详情(内容 + 执行记录);详情内有「执行」「删除」按钮
131
- - 执行真实启动会话(会话列表可见 transcript);卡片状态随真实执行进度变化;详情可跳转到执行会话
132
- - 删除有确认环节,删除后本地存储同步移除
133
- - 定时任务:cron 配置/预设/校验、下次运行时间、到点自动真实执行、状态回写、定时卡片标识、刷新后调度恢复(浏览器端调度,标签页需保持打开)
134
- - 任务级执行目标:工作区/模式/权限 钉子刷新后仍在,驱动执行会话;钉不住的钉子(工作区缺失、预设被锁、权限命令无人认领)在发 Prompt 前失败,执行记录可见原因
135
- - 一键挂载/卸载;卸载后 GUI 恢复原状,其它 managed 段不受影响
136
- - README + 覆盖存储读写/状态流转/执行触发/cron 解析/调度器的自动化测试
91
+ 设置 `DSH_POWER_SMOKE=1` 可在 Windows、macOS 或 Linux 上显式启用原生 helper smoke:真实启动固定 helper、等待 ready、在清理路径释放并确认进程退出,不修改系统电源计划。Linux 会先以有界超时探测 systemd-logind;没有可用 system bus 时原生部分跳过,纯逻辑测试仍可运行。
92
+
93
+ ## 手工验证
94
+
95
+ 1. 挂载插件并重启 `dsh web`,打开任务看板,确认 Host 时区和电源状态可见。
96
+ 2. 新建并编辑任务;刷新或打开第二个同源标签页,确认两者显示同一 Host revision。
97
+ 3. 执行一个钉住工作区、预设和权限的任务;确认出现新会话,并由该会话的 `turn/end` 历史结算任务。
98
+ 4. 启用一个即将到期的 cron,关闭全部浏览器页面,确认 Host 仍只创建并结算一次 execution。
99
+ 5. 让 Host 停止并错过一个 cron 触发点,重启后确认该次被跳过,`nextRunAt` 从当前 Host 时间向后滚动。
100
+ 6. 开启 `preventIdleSleep` 并运行长任务,让显示器自动熄灭;恢复显示后确认会话继续且 execution 已结算。
101
+ 7. 关闭设置并禁用所有计划,再停止 DSH,确认 helper 退出;macOS 可用 `pmset -g assertions` 辅助确认插件没有 display-sleep assertion。
102
+ 8. Linux 可用 `systemd-inhibit --list` 确认只存在 `idle`/`block` 条目;显示器仍按桌面设置关闭,手动睡眠和合盖仍由系统策略处理。
103
+
104
+ ## 已知限制
105
+
106
+ - Host 停止、系统睡眠或长暂停期间错过的触发点会跳过,绝不排队补跑。
107
+ - 同一任务已在运行时会跳过到期出现并滚动到下一 cron 匹配点;任务运行不并发、不排队。
108
+ - DST 采用 Host 本地墙上时钟语义:春季跳时中不存在的分钟会跳过,秋季回拨中重复的分钟不会执行第二次。
109
+ - 电源保护只阻止空闲系统睡眠,明确允许显示器睡眠与锁屏。
110
+ - 合盖、手动睡眠、休眠、关机、低电量强制睡眠和企业电源策略不在保证范围内。
111
+ - 插件不创建唤醒定时器,也不能唤醒已经睡眠的机器。
112
+ - Linux 需要 systemd-logind 及允许当前用户取得 idle block lock 的策略;容器、WSL、无 system bus 或非 systemd 系统可能显示 `unsupported` 或 `error`。桌面环境是否把 logind idle lock 与显示器空闲联动属于其自身策略,插件不请求屏保或显示器 inhibitor。
113
+ - 已启用计划会从未来触发点之前持续持锁,因此可能增加电池消耗。
114
+ - Host 执行消耗与普通 DSH agent 会话相同的 API 额度。