@linxin666/dsh-client-ui-task-board 0.1.16 → 0.1.18
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.
- package/README.i18n.yaml +2 -2
- package/README.md +11 -7
- package/README.zh.md +11 -7
- package/lib/client.js +821 -145
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +11 -0
- package/lib/types/client/locales.d.ts +21 -0
- package/lib/types/client/schedule-presets.d.ts +10 -0
- package/lib/types/core/controller.d.ts +57 -1
- package/lib/types/core/execution.d.ts +65 -4
- package/lib/types/core/scheduler.d.ts +7 -0
- package/lib/types/core/store.d.ts +1 -1
- package/lib/types/core/tasks.d.ts +43 -0
- package/lib/types/core/use-cases/task-archive.d.ts +22 -0
- package/lib/types/core/use-cases/task-create.d.ts +1 -6
- package/lib/types/core/use-cases/task-update.d.ts +9 -4
- package/lib/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +50 -47
- package/src/client/board/NewTaskModal.tsx +136 -3
- package/src/client/board/TaskBoard.tsx +48 -19
- package/src/client/board/TaskDetail.tsx +97 -8
- package/src/client/board.module.css +16 -0
- package/src/client/index.ts +71 -2
- package/src/client/locales.ts +42 -0
- package/src/client/schedule-presets.ts +13 -0
- package/src/client/settings-card.module.css +9 -0
- package/src/core/controller.ts +92 -2
- package/src/core/execution.ts +179 -12
- package/src/core/scheduler.ts +15 -0
- package/src/core/store.ts +16 -2
- package/src/core/tasks.ts +56 -0
- package/src/core/use-cases/task-archive.ts +48 -0
- package/src/core/use-cases/task-create.ts +12 -3
- package/src/core/use-cases/task-update.ts +34 -5
- package/src/index.ts +1 -1
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import z from "schemastery";
|
|
|
5
5
|
const SECTION_ORDER = 200;
|
|
6
6
|
const inject = ["systemPrompt"];
|
|
7
7
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
8
|
-
const TASK_BOARD_GUIDANCE = "本机已安装 dsh-task-board 插件(DSH Web GUI 的任务看板):侧边栏「任务看板」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-task-board)统一维护,经聚合包 web-ui-all 一键安装。能力:多列看板管理任务;任务可真实执行(驱动 agent
|
|
8
|
+
const TASK_BOARD_GUIDANCE = "本机已安装 dsh-task-board 插件(DSH Web GUI 的任务看板):侧边栏「任务看板」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-task-board)统一维护,经聚合包 web-ui-all 一键安装。能力:多列看板管理任务;任务可真实执行(驱动 agent 会话);任务可钉住执行目标——工作区 / 模式(agent 预设)/ 权限(read-only / workspace-write / danger-full-access),缺省用运行时默认;任务支持 5 段 cron 定时执行(如 0 23 * * *);数据存浏览器 localStorage(键 dsh.taskBoard.v1)。限制:定时调度在浏览器端,需 GUI 标签页打开,错过即跳过;执行消耗 API 额度。用户提到「任务看板 / 看板 / 定时任务」时即指本插件,请据此协作。";
|
|
9
9
|
/**
|
|
10
10
|
* Settings namespace of the board's announcement capability — the section the
|
|
11
11
|
* web settings surface edits. Spelled here rather than imported: the browser
|
|
@@ -28,6 +28,17 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
|
28
28
|
onSave: () => void;
|
|
29
29
|
/** Drop every staged edit. */
|
|
30
30
|
onDiscard: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Render the controls expanded on arrival (still collapsible). Defaults to
|
|
33
|
+
* true: promoted first-level sections show their settings immediately.
|
|
34
|
+
*/
|
|
35
|
+
defaultOpen?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Render without the collapse affordance: a static header and an always
|
|
38
|
+
* visible body. Used by first-level settings sections whose nav entry
|
|
39
|
+
* already provides the selection.
|
|
40
|
+
*/
|
|
41
|
+
alwaysOpen?: boolean;
|
|
31
42
|
/** The plugin's controls. */
|
|
32
43
|
children: ReactNode;
|
|
33
44
|
}
|
|
@@ -13,6 +13,10 @@ export declare const zh: {
|
|
|
13
13
|
'board.search': string;
|
|
14
14
|
'board.empty': string;
|
|
15
15
|
'board.filterAll': string;
|
|
16
|
+
'board.archive': string;
|
|
17
|
+
'board.archiveView': string;
|
|
18
|
+
'board.backToBoard': string;
|
|
19
|
+
'archive.empty': string;
|
|
16
20
|
'board.status': string;
|
|
17
21
|
'board.status.backlog': string;
|
|
18
22
|
'board.status.todo': string;
|
|
@@ -40,6 +44,9 @@ export declare const zh: {
|
|
|
40
44
|
'detail.run': string;
|
|
41
45
|
'detail.rerun': string;
|
|
42
46
|
'detail.delete': string;
|
|
47
|
+
'detail.archive': string;
|
|
48
|
+
'detail.restore': string;
|
|
49
|
+
'detail.archivedAt': string;
|
|
43
50
|
'detail.viewSession': string;
|
|
44
51
|
'detail.noSession': string;
|
|
45
52
|
'detail.executionStarted': string;
|
|
@@ -72,6 +79,20 @@ export declare const zh: {
|
|
|
72
79
|
'detail.schedule.notScheduled': string;
|
|
73
80
|
'detail.schedule.dueSoon': string;
|
|
74
81
|
'card.scheduled': string;
|
|
82
|
+
'new.workspace': string;
|
|
83
|
+
'new.mode': string;
|
|
84
|
+
'new.permission': string;
|
|
85
|
+
'exec.workspace.recent': string;
|
|
86
|
+
'exec.mode.default': string;
|
|
87
|
+
'exec.mode.defaultSuffix': string;
|
|
88
|
+
'exec.mode.brokenSuffix': string;
|
|
89
|
+
'exec.mode.removed': string;
|
|
90
|
+
'exec.permission.default': string;
|
|
91
|
+
'exec.permission.read-only': string;
|
|
92
|
+
'exec.permission.workspace-write': string;
|
|
93
|
+
'exec.permission.danger-full-access': string;
|
|
94
|
+
'detail.executionSettings': string;
|
|
95
|
+
'exec.hint': string;
|
|
75
96
|
'settings.title': string;
|
|
76
97
|
'settings.description': string;
|
|
77
98
|
'settings.enabled': string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared scheduled-run presets for the schedule editors (the new-task dialog
|
|
3
|
+
* and the task detail panel): cron expression → locale label.
|
|
4
|
+
*/
|
|
5
|
+
import type { TaskBoardKey } from './locales.ts';
|
|
6
|
+
/** Common scheduled-run presets (cron → locale label). */
|
|
7
|
+
export declare const SCHEDULE_PRESETS: ReadonlyArray<{
|
|
8
|
+
cron: string;
|
|
9
|
+
label: TaskBoardKey;
|
|
10
|
+
}>;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { ExecutionService } from './execution.ts';
|
|
17
17
|
import type { TaskStore } from './store.ts';
|
|
18
18
|
import { type NewTaskInput, type TaskRecord, type TaskStatus } from './tasks.ts';
|
|
19
|
+
import { type TaskUpdatePatch } from './use-cases/task-update.ts';
|
|
19
20
|
/** The sessions face the controller needs for navigation awareness. */
|
|
20
21
|
export interface SessionsControllerFace {
|
|
21
22
|
list: {
|
|
@@ -39,11 +40,35 @@ export interface ControllerDeps {
|
|
|
39
40
|
/** Debounce (ms) for session-list-changed reconciles; defaults to 350. */
|
|
40
41
|
reconcileDebounceMs?: number;
|
|
41
42
|
}
|
|
43
|
+
/** One workspace option the execution-target pickers offer. */
|
|
44
|
+
export interface ExecutionWorkspaceOption {
|
|
45
|
+
workspaceId: string;
|
|
46
|
+
/** Display label (workspace title; the wiring falls back to the path). */
|
|
47
|
+
title: string;
|
|
48
|
+
}
|
|
49
|
+
/** One agent-preset option the execution-target pickers offer. */
|
|
50
|
+
export interface ExecutionPresetOption {
|
|
51
|
+
id: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
/** Why this preset cannot compose a session; the pickers disable it. */
|
|
55
|
+
broken?: string;
|
|
56
|
+
isDefault: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** The execution-target option sets the UI feeds into the controller. */
|
|
59
|
+
export interface ExecutionOptionsSnapshot {
|
|
60
|
+
workspaces: readonly ExecutionWorkspaceOption[];
|
|
61
|
+
presets: readonly ExecutionPresetOption[];
|
|
62
|
+
}
|
|
42
63
|
/** Immutable controller snapshot for UI subscriptions. */
|
|
43
64
|
export interface ControllerSnapshot {
|
|
44
65
|
tasks: readonly TaskRecord[];
|
|
45
66
|
boardOpen: boolean;
|
|
67
|
+
/** True when the board shows the archive view instead of the columns. */
|
|
68
|
+
archiveView: boolean;
|
|
46
69
|
selectedTaskId: string | undefined;
|
|
70
|
+
/** Picker option sets (workspace list + agent-preset roster). */
|
|
71
|
+
executionOptions: ExecutionOptionsSnapshot;
|
|
47
72
|
}
|
|
48
73
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
49
74
|
export declare function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined;
|
|
@@ -55,7 +80,9 @@ export declare class BoardController {
|
|
|
55
80
|
private readonly deps;
|
|
56
81
|
private tasks;
|
|
57
82
|
private boardOpen;
|
|
83
|
+
private archiveView;
|
|
58
84
|
private selectedTaskId;
|
|
85
|
+
private executionOptions;
|
|
59
86
|
private listeners;
|
|
60
87
|
private disposers;
|
|
61
88
|
private readonly now;
|
|
@@ -71,12 +98,32 @@ export declare class BoardController {
|
|
|
71
98
|
openBoard(): void;
|
|
72
99
|
closeBoard(): void;
|
|
73
100
|
toggleBoard(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Switch between the kanban columns and the archive view. Leaving the
|
|
103
|
+
* archive view with an archived task still selected closes the selection —
|
|
104
|
+
* the detail overlay must not linger over a task that is off-board.
|
|
105
|
+
*/
|
|
106
|
+
toggleArchiveView(): void;
|
|
74
107
|
openTask(id: string): void;
|
|
75
108
|
closeTask(): void;
|
|
76
109
|
createTask(input: NewTaskInput): TaskRecord | undefined;
|
|
77
|
-
updateTask(id: string, patch:
|
|
110
|
+
updateTask(id: string, patch: TaskUpdatePatch): void;
|
|
111
|
+
/**
|
|
112
|
+
* Replace (a part of) the picker option sets the UI feeds (workspace list
|
|
113
|
+
* and agent-preset roster come from the runtime, not the ledger).
|
|
114
|
+
*/
|
|
115
|
+
setExecutionOptions(patch: Partial<ExecutionOptionsSnapshot>): void;
|
|
78
116
|
moveTask(id: string, status: TaskStatus): void;
|
|
79
117
|
deleteTask(id: string): void;
|
|
118
|
+
/**
|
|
119
|
+
* Archive a settled task (done/failed). Running or on-board-unsettled
|
|
120
|
+
* tasks are refused so the runner keeps exclusive ownership of their
|
|
121
|
+
* lifecycle.
|
|
122
|
+
* @returns true when applied.
|
|
123
|
+
*/
|
|
124
|
+
archiveTask(id: string): boolean;
|
|
125
|
+
/** Restore an archived task back onto the board (same status column). */
|
|
126
|
+
restoreTask(id: string): boolean;
|
|
80
127
|
/**
|
|
81
128
|
* Update a task's schedule rule. A blank or invalid cron expression is
|
|
82
129
|
* rejected (returns false, state untouched). When the rule ends up enabled
|
|
@@ -96,6 +143,15 @@ export declare class BoardController {
|
|
|
96
143
|
* schedule rule (it was deleted mid-tick, for example).
|
|
97
144
|
*/
|
|
98
145
|
applyScheduleNextRun(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void;
|
|
146
|
+
/**
|
|
147
|
+
* Reload the ledger from the persisted store without notifying subscribers.
|
|
148
|
+
* The scheduler calls this before every tick so a task deleted in another
|
|
149
|
+
* tab (or a stale in-memory copy) can never be fired from this tab: the
|
|
150
|
+
* fire decision and the subsequent roll-forward both run on the freshest
|
|
151
|
+
* persisted truth. Deliberately silent — same-origin external changes still
|
|
152
|
+
* reach subscribers through the storage-event subscription.
|
|
153
|
+
*/
|
|
154
|
+
reloadFromStore(): void;
|
|
99
155
|
/**
|
|
100
156
|
* Jump to an execution's session transcript. Selecting the session changes
|
|
101
157
|
* `current`, which closes the board (the conversation view takes over).
|
|
@@ -14,22 +14,40 @@
|
|
|
14
14
|
* so tests drive it with plain fakes.
|
|
15
15
|
*/
|
|
16
16
|
import type { ExecutionRecord, TaskRecord } from './tasks.ts';
|
|
17
|
+
/** One list-row summary the execution service reads (the narrow slice of a SessionSummary). */
|
|
18
|
+
export interface ExecutionSessionSummary {
|
|
19
|
+
running: boolean;
|
|
20
|
+
completed?: boolean;
|
|
21
|
+
/** Empty-log bit: the preset can only be recomposed while the session is blank. */
|
|
22
|
+
blank?: boolean;
|
|
23
|
+
/** The preset the session currently runs (absent on deployments without presets). */
|
|
24
|
+
agentPreset?: string;
|
|
25
|
+
}
|
|
17
26
|
/** The narrow sessions face the service needs. */
|
|
18
27
|
export interface SessionsExecutionFace {
|
|
19
28
|
list: {
|
|
20
29
|
getSnapshot(): {
|
|
21
30
|
/** Baseline arrival lifecycle — 'pending' until the host list has loaded. */
|
|
22
31
|
phase: 'pending' | 'ready';
|
|
23
|
-
byId: Record<string,
|
|
24
|
-
running: boolean;
|
|
25
|
-
completed?: boolean;
|
|
26
|
-
}>;
|
|
32
|
+
byId: Record<string, ExecutionSessionSummary>;
|
|
27
33
|
};
|
|
28
34
|
subscribe(fn: () => void): () => void;
|
|
29
35
|
};
|
|
30
36
|
binding(id: string): {
|
|
31
37
|
session: SessionDriver;
|
|
32
38
|
} | undefined;
|
|
39
|
+
/** Record a host-confirmed preset switch so the list label moves immediately. */
|
|
40
|
+
noteAgentPreset?(sessionId: string, agentPreset: string): void;
|
|
41
|
+
}
|
|
42
|
+
/** The narrow agent-preset wire face the service needs (`agentPreset.select`). */
|
|
43
|
+
export interface PresetsExecutionFace {
|
|
44
|
+
/** Recompose a blank session's agent from a preset. */
|
|
45
|
+
select(sessionId: string, agentPreset: string): Promise<{
|
|
46
|
+
ok: true;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
error: unknown;
|
|
50
|
+
}>;
|
|
33
51
|
}
|
|
34
52
|
/** The narrow workspaces face the service needs. */
|
|
35
53
|
export interface WorkspacesExecutionFace {
|
|
@@ -63,6 +81,18 @@ export interface SessionDriver {
|
|
|
63
81
|
ok: false;
|
|
64
82
|
error: unknown;
|
|
65
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Admit one slash-command line against the session's agent (the
|
|
86
|
+
* `/permission <id>` mechanism). `matched` reports whether a command
|
|
87
|
+
* claimed the line.
|
|
88
|
+
*/
|
|
89
|
+
command(line: string): Promise<{
|
|
90
|
+
ok: true;
|
|
91
|
+
matched: boolean;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
error: unknown;
|
|
95
|
+
}>;
|
|
66
96
|
getSnapshot(): {
|
|
67
97
|
running: boolean;
|
|
68
98
|
lastAgentError: string | null;
|
|
@@ -74,6 +104,8 @@ export interface SessionDriver {
|
|
|
74
104
|
export interface ExecutionEnvironment {
|
|
75
105
|
sessions: SessionsExecutionFace;
|
|
76
106
|
workspaces: WorkspacesExecutionFace;
|
|
107
|
+
/** Agent-preset wire face; absent on deployments without preset support. */
|
|
108
|
+
presets?: PresetsExecutionFace;
|
|
77
109
|
/** Raw-history reader for failure detection of never-opened sessions. */
|
|
78
110
|
history?: HistoryExecutionFace;
|
|
79
111
|
}
|
|
@@ -101,9 +133,38 @@ export type ExecutionEvent = {
|
|
|
101
133
|
*/
|
|
102
134
|
export declare class ExecutionService {
|
|
103
135
|
private readonly env;
|
|
136
|
+
/**
|
|
137
|
+
* In-flight preset switches keyed by `sessionId\u0000mode`. The session
|
|
138
|
+
* list mirror can lag behind a just-applied switch, so concurrent runs
|
|
139
|
+
* against one blank session would otherwise each issue the same select
|
|
140
|
+
* RPC (an amplified storm); sharing one in-flight call keeps the wire to
|
|
141
|
+
* a single request. The settled entry is forgotten so a later run can
|
|
142
|
+
* switch again.
|
|
143
|
+
*/
|
|
144
|
+
private readonly presetSwitches;
|
|
104
145
|
/** @param env - the runtime faces (real or fake). */
|
|
105
146
|
constructor(env: ExecutionEnvironment);
|
|
106
147
|
run(task: TaskRecord, execution: ExecutionRecord, onEvent: (event: ExecutionEvent) => void): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Recompose the execution session's agent from the task-pinned preset.
|
|
150
|
+
* No-op when the task pins none or the session already runs it; fails the
|
|
151
|
+
* run when the session is no longer blank, the preset face is missing, or
|
|
152
|
+
* the wire refuses.
|
|
153
|
+
*/
|
|
154
|
+
private applyMode;
|
|
155
|
+
/**
|
|
156
|
+
* One in-flight `select` per (session, preset): concurrent runs against
|
|
157
|
+
* the same blank session share the same wire call instead of each issuing
|
|
158
|
+
* a duplicate RPC. The entry is removed once the call settles, so a later
|
|
159
|
+
* run (after a shared failure, say) issues a fresh switch.
|
|
160
|
+
*/
|
|
161
|
+
private switchPreset;
|
|
162
|
+
/**
|
|
163
|
+
* Apply the task-pinned permission preset through the `/permission <id>`
|
|
164
|
+
* slash command. No-op when the task pins none; fails the run when the
|
|
165
|
+
* admission is rejected or no command claimed the line.
|
|
166
|
+
*/
|
|
167
|
+
private applyPermission;
|
|
107
168
|
/**
|
|
108
169
|
* Inspect a reloaded/background task that was left 'running' and emit a
|
|
109
170
|
* settled event when its session already finished.
|
|
@@ -3,6 +3,13 @@ import type { TaskRecord } from './tasks.ts';
|
|
|
3
3
|
export interface SchedulerDeps {
|
|
4
4
|
/** Read the current task ledger (the controller snapshot). */
|
|
5
5
|
tasks(): readonly TaskRecord[];
|
|
6
|
+
/**
|
|
7
|
+
* Re-sync the task ledger from its persisted source before the tick decides
|
|
8
|
+
* what is due. The wiring passes the controller's store reload so a task
|
|
9
|
+
* deleted elsewhere can never be fired from a stale in-memory copy.
|
|
10
|
+
* Optional: absent in pure in-memory harnesses.
|
|
11
|
+
*/
|
|
12
|
+
refresh?: () => void;
|
|
6
13
|
/** Clock; defaults to Date.now in the controller wiring. */
|
|
7
14
|
now(): number;
|
|
8
15
|
/** Trigger one task's real execution (the controller's runTask); resolves
|
|
@@ -60,12 +60,55 @@ export interface TaskRecord {
|
|
|
60
60
|
executions: ExecutionRecord[];
|
|
61
61
|
/** Optional scheduled-run rule (absent on tasks without a schedule). */
|
|
62
62
|
schedule?: ScheduleRule;
|
|
63
|
+
/**
|
|
64
|
+
* Workspace the execution must run in (a workspace-list id); absent means
|
|
65
|
+
* the recent-workspace fallback at execution time.
|
|
66
|
+
*/
|
|
67
|
+
workspaceId?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Agent preset the execution session must be composed from (an
|
|
70
|
+
* `agentPreset.list` id); absent means the deployment default.
|
|
71
|
+
*/
|
|
72
|
+
mode?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Permission preset applied to the execution session through the
|
|
75
|
+
* `/permission <id>` slash command; absent leaves the session default.
|
|
76
|
+
*/
|
|
77
|
+
permission?: TaskPermission;
|
|
78
|
+
/**
|
|
79
|
+
* When the task was archived (ms epoch). Archived tasks keep their status
|
|
80
|
+
* and execution history but leave the main board; absent means on-board.
|
|
81
|
+
*/
|
|
82
|
+
archivedAt?: number;
|
|
63
83
|
}
|
|
84
|
+
/** Statuses a settled task may be archived from. */
|
|
85
|
+
export declare const ARCHIVABLE_STATUSES: readonly TaskStatus[];
|
|
86
|
+
/** Permission presets a task may pin on its execution session (the `/permission <id>` ids). */
|
|
87
|
+
export declare const TASK_PERMISSIONS: readonly ["read-only", "workspace-write", "danger-full-access"];
|
|
88
|
+
/** One permission preset id. */
|
|
89
|
+
export type TaskPermission = typeof TASK_PERMISSIONS[number];
|
|
90
|
+
/** Whether an unknown value is a known permission preset id. */
|
|
91
|
+
export declare function isTaskPermission(value: unknown): value is TaskPermission;
|
|
64
92
|
/** Input for creating a task. */
|
|
65
93
|
export interface NewTaskInput {
|
|
66
94
|
title: string;
|
|
67
95
|
description: string;
|
|
68
96
|
prompt: string;
|
|
97
|
+
/** Workspace the execution must run in; empty/absent = the recent workspace. */
|
|
98
|
+
workspaceId?: string;
|
|
99
|
+
/** Agent preset the execution session must be composed from; empty/absent = deployment default. */
|
|
100
|
+
mode?: string;
|
|
101
|
+
/** Permission preset applied to the execution session; absent = session default. */
|
|
102
|
+
permission?: TaskPermission;
|
|
103
|
+
/**
|
|
104
|
+
* Optional scheduled-run rule requested at creation time (the new-task
|
|
105
|
+
* dialog): an enable flag plus a 5-field cron expression. The create use
|
|
106
|
+
* case arms it only when enabled and the expression is valid.
|
|
107
|
+
*/
|
|
108
|
+
schedule?: {
|
|
109
|
+
enabled: boolean;
|
|
110
|
+
cron: string;
|
|
111
|
+
};
|
|
69
112
|
}
|
|
70
113
|
/** The five kanban columns, in display order. */
|
|
71
114
|
export declare const COLUMNS: readonly {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive/restore task use case: move a settled (done/failed) task off the
|
|
3
|
+
* main board and back. Purely a marker transition — the task keeps its
|
|
4
|
+
* status, execution history and transcript references, so archiving never
|
|
5
|
+
* loses traceability and restoring lands it in the exact column it left.
|
|
6
|
+
*/
|
|
7
|
+
import type { TaskRecord } from '../tasks.ts';
|
|
8
|
+
/** Result of an archive transition. */
|
|
9
|
+
export interface ArchiveTaskResult {
|
|
10
|
+
/** The next ledger. */
|
|
11
|
+
tasks: readonly TaskRecord[];
|
|
12
|
+
/** Whether the archive was applied (false = unknown task / not archivable). */
|
|
13
|
+
archived: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Archive one task: only settled statuses (done/failed) can be archived;
|
|
17
|
+
* a running or not-yet-settled task stays on the board (its runner still
|
|
18
|
+
* owns its lifecycle). Already-archived tasks are a no-op.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applyArchiveTask(tasks: readonly TaskRecord[], id: string, now: number): ArchiveTaskResult;
|
|
21
|
+
/** Restore one task back onto the main board (clears the archive marker). */
|
|
22
|
+
export declare function applyRestoreTask(tasks: readonly TaskRecord[], id: string, now: number): ArchiveTaskResult;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create-task use case: mint a new task from user input, rejecting a blank
|
|
3
|
-
* title. Pure ledger transition (no persistence or notify — the controller
|
|
4
|
-
* orchestrates those), so it is unit-testable without any runtime face.
|
|
5
|
-
*/
|
|
6
1
|
import { type NewTaskInput, type TaskRecord } from '../tasks.ts';
|
|
7
2
|
/** Result of a create transition: the new task (when accepted) + the next ledger. */
|
|
8
3
|
export interface CreateTaskResult {
|
|
@@ -15,7 +10,7 @@ export interface CreateTaskResult {
|
|
|
15
10
|
* Apply a create against the current ledger. Returns the new task and the
|
|
16
11
|
* appended ledger, or the unchanged ledger when the title is blank.
|
|
17
12
|
* @param tasks - current ledger.
|
|
18
|
-
* @param input - raw user input (title/description/prompt).
|
|
13
|
+
* @param input - raw user input (title/description/prompt + optional schedule).
|
|
19
14
|
* @param now - clock instant (ms epoch).
|
|
20
15
|
* @param id - minted task id.
|
|
21
16
|
*/
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Update-task use case: apply an editable-field patch (title/description/
|
|
3
|
-
* prompt
|
|
4
|
-
* notify — the
|
|
3
|
+
* prompt plus the execution targets workspaceId/mode/permission) with a
|
|
4
|
+
* fresh updatedAt. Pure ledger transition (no persistence or notify — the
|
|
5
|
+
* controller orchestrates those).
|
|
6
|
+
*
|
|
7
|
+
* An explicit `undefined` in the patch clears the field (the task falls
|
|
8
|
+
* back to the runtime default); an unknown permission string is ignored so
|
|
9
|
+
* stale UI can never persist a value the execution service rejects.
|
|
5
10
|
*/
|
|
6
|
-
import type
|
|
11
|
+
import { type TaskRecord } from '../tasks.ts';
|
|
7
12
|
/** Editable fields on a task (the update patch surface). */
|
|
8
|
-
export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt'>>;
|
|
13
|
+
export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission'>>;
|
|
9
14
|
/**
|
|
10
15
|
* Apply an update across the ledger. Tasks that do not match the id are left
|
|
11
16
|
* untouched; the matched task receives the patch plus a fresh updatedAt.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
14
14
|
import z from 'schemastery';
|
|
15
15
|
export declare const inject: string[];
|
|
16
16
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
17
|
-
export declare const TASK_BOARD_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-task-board \u63D2\u4EF6\uFF08DSH Web GUI \u7684\u4EFB\u52A1\u770B\u677F\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u4EFB\u52A1\u770B\u677F\u300D\u5165\u53E3\uFF1B\u5728 dsh-web-ui \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-task-board\uFF09\u7EDF\u4E00\u7EF4\u62A4\uFF0C\u7ECF\u805A\u5408\u5305 web-ui-all \u4E00\u952E\u5B89\u88C5\u3002\u80FD\u529B\uFF1A\u591A\u5217\u770B\u677F\u7BA1\u7406\u4EFB\u52A1\uFF1B\u4EFB\u52A1\u53EF\u771F\u5B9E\u6267\u884C\uFF08\u9A71\u52A8 agent \u4F1A\u8BDD\uFF09\uFF1B\u4EFB\u52A1\u652F\u6301 5 \u6BB5 cron \u5B9A\u65F6\u6267\u884C\uFF08\u5982 0 23 * * *\uFF09\uFF1B\u6570\u636E\u5B58\u6D4F\u89C8\u5668 localStorage\uFF08\u952E dsh.taskBoard.v1\uFF09\u3002\u9650\u5236\uFF1A\u5B9A\u65F6\u8C03\u5EA6\u5728\u6D4F\u89C8\u5668\u7AEF\uFF0C\u9700 GUI \u6807\u7B7E\u9875\u6253\u5F00\uFF0C\u9519\u8FC7\u5373\u8DF3\u8FC7\uFF1B\u6267\u884C\u6D88\u8017 API \u989D\u5EA6\u3002\u7528\u6237\u63D0\u5230\u300C\u4EFB\u52A1\u770B\u677F / \u770B\u677F / \u5B9A\u65F6\u4EFB\u52A1\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
17
|
+
export declare const TASK_BOARD_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-task-board \u63D2\u4EF6\uFF08DSH Web GUI \u7684\u4EFB\u52A1\u770B\u677F\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u4EFB\u52A1\u770B\u677F\u300D\u5165\u53E3\uFF1B\u5728 dsh-web-ui \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-task-board\uFF09\u7EDF\u4E00\u7EF4\u62A4\uFF0C\u7ECF\u805A\u5408\u5305 web-ui-all \u4E00\u952E\u5B89\u88C5\u3002\u80FD\u529B\uFF1A\u591A\u5217\u770B\u677F\u7BA1\u7406\u4EFB\u52A1\uFF1B\u4EFB\u52A1\u53EF\u771F\u5B9E\u6267\u884C\uFF08\u9A71\u52A8 agent \u4F1A\u8BDD\uFF09\uFF1B\u4EFB\u52A1\u53EF\u9489\u4F4F\u6267\u884C\u76EE\u6807\u2014\u2014\u5DE5\u4F5C\u533A / \u6A21\u5F0F\uFF08agent \u9884\u8BBE\uFF09/ \u6743\u9650\uFF08read-only / workspace-write / danger-full-access\uFF09\uFF0C\u7F3A\u7701\u7528\u8FD0\u884C\u65F6\u9ED8\u8BA4\uFF1B\u4EFB\u52A1\u652F\u6301 5 \u6BB5 cron \u5B9A\u65F6\u6267\u884C\uFF08\u5982 0 23 * * *\uFF09\uFF1B\u6570\u636E\u5B58\u6D4F\u89C8\u5668 localStorage\uFF08\u952E dsh.taskBoard.v1\uFF09\u3002\u9650\u5236\uFF1A\u5B9A\u65F6\u8C03\u5EA6\u5728\u6D4F\u89C8\u5668\u7AEF\uFF0C\u9700 GUI \u6807\u7B7E\u9875\u6253\u5F00\uFF0C\u9519\u8FC7\u5373\u8DF3\u8FC7\uFF1B\u6267\u884C\u6D88\u8017 API \u989D\u5EA6\u3002\u7528\u6237\u63D0\u5230\u300C\u4EFB\u52A1\u770B\u677F / \u770B\u677F / \u5B9A\u65F6\u4EFB\u52A1\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
18
18
|
/**
|
|
19
19
|
* Settings namespace of the board's announcement capability — the section the
|
|
20
20
|
* web settings surface edits. Spelled here rather than imported: the browser
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence, task details, and real execution through dsh sessions (session.prompt). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -45,6 +45,17 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
|
45
45
|
onSave: () => void
|
|
46
46
|
/** Drop every staged edit. */
|
|
47
47
|
onDiscard: () => void
|
|
48
|
+
/**
|
|
49
|
+
* Render the controls expanded on arrival (still collapsible). Defaults to
|
|
50
|
+
* true: promoted first-level sections show their settings immediately.
|
|
51
|
+
*/
|
|
52
|
+
defaultOpen?: boolean
|
|
53
|
+
/**
|
|
54
|
+
* Render without the collapse affordance: a static header and an always
|
|
55
|
+
* visible body. Used by first-level settings sections whose nav entry
|
|
56
|
+
* already provides the selection.
|
|
57
|
+
*/
|
|
58
|
+
alwaysOpen?: boolean
|
|
48
59
|
/** The plugin's controls. */
|
|
49
60
|
children: ReactNode
|
|
50
61
|
}
|
|
@@ -55,57 +66,27 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
|
55
66
|
* @returns the card, or nothing while the namespace is still loading.
|
|
56
67
|
*/
|
|
57
68
|
export function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>) {
|
|
58
|
-
const [open, setOpen] = useState(
|
|
59
|
-
const { state } = props
|
|
69
|
+
const [open, setOpen] = useState(props.defaultOpen ?? true)
|
|
70
|
+
const { state, alwaysOpen } = props
|
|
60
71
|
if (!state.available) return null
|
|
61
72
|
const title = props.t(props.titleKey)
|
|
62
73
|
const description = props.t(props.descriptionKey)
|
|
63
74
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
onClick={() => { setOpen(!open) }}
|
|
78
|
-
>
|
|
79
|
-
<span className={css.headText}>
|
|
80
|
-
<span className={css.name} title={title}>{title}</span>
|
|
81
|
-
<span className={css.description} title={description}>{description}</span>
|
|
82
|
-
</span>
|
|
83
|
-
<svg
|
|
84
|
-
width="14"
|
|
85
|
-
height="14"
|
|
86
|
-
viewBox="0 0 14 14"
|
|
87
|
-
fill="none"
|
|
88
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
-
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
90
|
-
>
|
|
91
|
-
<path
|
|
92
|
-
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
93
|
-
fill="currentColor"
|
|
94
|
-
/>
|
|
95
|
-
</svg>
|
|
96
|
-
</button>
|
|
97
|
-
{open
|
|
98
|
-
? (
|
|
99
|
-
<div className={css.body}>
|
|
100
|
-
<p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
|
|
101
|
-
</div>
|
|
102
|
-
)
|
|
103
|
-
: null}
|
|
104
|
-
</li>
|
|
75
|
+
const expanded = alwaysOpen === true || open
|
|
76
|
+
const cardClass = expanded ? `${css.cardOpen} ${css.card}` : css.card
|
|
77
|
+
// With alwaysOpen the nav entry already provides the selection, so the
|
|
78
|
+
// header is a static title row instead of a disclosure button.
|
|
79
|
+
const header = alwaysOpen === true
|
|
80
|
+
? (
|
|
81
|
+
<div className={css.headerStatic}>
|
|
82
|
+
<span className={css.headText}>
|
|
83
|
+
<span className={css.name} title={title}>{title}</span>
|
|
84
|
+
<span className={css.description} title={description}>{description}</span>
|
|
85
|
+
</span>
|
|
86
|
+
{state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}
|
|
87
|
+
</div>
|
|
105
88
|
)
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<li className={cardClass}>
|
|
89
|
+
: (
|
|
109
90
|
<button
|
|
110
91
|
type="button"
|
|
111
92
|
className={css.header}
|
|
@@ -132,7 +113,29 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
|
|
|
132
113
|
/>
|
|
133
114
|
</svg>
|
|
134
115
|
</button>
|
|
135
|
-
|
|
116
|
+
)
|
|
117
|
+
// The namespace exists but the Host does not serve it to this client (the
|
|
118
|
+
// official settings allowlist omits third-party namespaces): show a card
|
|
119
|
+
// that explains the gap instead of vanishing, so a missing card never
|
|
120
|
+
// reads as a missing plugin.
|
|
121
|
+
if (!state.exposed) {
|
|
122
|
+
return (
|
|
123
|
+
<li className={cardClass}>
|
|
124
|
+
{header}
|
|
125
|
+
{expanded
|
|
126
|
+
? (
|
|
127
|
+
<div className={css.body}>
|
|
128
|
+
<p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
: null}
|
|
132
|
+
</li>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
return (
|
|
136
|
+
<li className={cardClass}>
|
|
137
|
+
{header}
|
|
138
|
+
{expanded
|
|
136
139
|
? (
|
|
137
140
|
<div className={css.body}>
|
|
138
141
|
{!state.writable ? <p className={css.readOnly} role="status">{props.t('settings.readOnly')}</p> : null}
|