@linxin666/dsh-client-ui-task-board 0.4.0 → 0.4.2
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 +24 -0
- package/README.zh.md +24 -0
- package/lib/client.js +917 -81
- package/lib/client.js.map +1 -1
- package/lib/index.js +1774 -1420
- package/lib/types/client/TaskBoardSettingsCard.d.ts +4 -0
- package/lib/types/client/board/LinkSubtaskModal.d.ts +11 -0
- package/lib/types/client/board/NewTaskModal.d.ts +7 -1
- package/lib/types/client/board/TaskCard.d.ts +11 -1
- package/lib/types/client/locales.d.ts +33 -0
- package/lib/types/core/controller.d.ts +30 -1
- package/lib/types/core/subtask.d.ts +122 -0
- package/lib/types/core/tasks.d.ts +46 -3
- package/lib/types/core/use-cases/task-archive.d.ts +10 -13
- package/lib/types/core/use-cases/task-create.d.ts +13 -4
- package/lib/types/core/use-cases/task-delete.d.ts +7 -1
- package/lib/types/core/use-cases/task-parent.d.ts +21 -0
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/host/agent-tools.d.ts +27 -0
- package/lib/types/host-ledger.d.ts +88 -4
- package/lib/types/host-runner.d.ts +39 -1
- package/lib/types/host-service.d.ts +47 -0
- package/lib/types/index.d.ts +83 -5
- package/lib/types/protocol.d.ts +14 -0
- package/package.json +23 -21
- package/src/client/TaskBoardSettingsCard.tsx +48 -3
- package/src/client/board/LinkSubtaskModal.tsx +82 -0
- package/src/client/board/NewTaskModal.tsx +40 -6
- package/src/client/board/TaskBoard.tsx +88 -8
- package/src/client/board/TaskCard.tsx +52 -2
- package/src/client/board/TaskDetail.tsx +124 -1
- package/src/client/board.module.css +96 -0
- package/src/client/host-api.ts +14 -4
- package/src/client/index.ts +11 -1
- package/src/client/locales.ts +70 -2
- package/src/core/controller.ts +69 -8
- package/src/core/store.ts +15 -0
- package/src/core/subtask.ts +286 -0
- package/src/core/tasks.ts +50 -2
- package/src/core/use-cases/task-archive.ts +46 -12
- package/src/core/use-cases/task-create.ts +42 -7
- package/src/core/use-cases/task-delete.ts +11 -1
- package/src/core/use-cases/task-parent.ts +64 -0
- package/src/core/use-cases/task-update.ts +3 -1
- package/src/host/agent-tools.ts +643 -0
- package/src/host-ledger.ts +323 -45
- package/src/host-runner.ts +141 -12
- package/src/host-service.ts +127 -10
- package/src/index.ts +181 -9
- package/src/protocol.ts +26 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExecutionRecord, type TaskRecord } from './core/tasks.ts';
|
|
1
|
+
import { type ExecutionOutcome, type ExecutionRecord, type TaskRecord } from './core/tasks.ts';
|
|
2
2
|
import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts';
|
|
3
3
|
import { type TaskPermission } from './core/handover.ts';
|
|
4
4
|
export interface LedgerState {
|
|
@@ -9,6 +9,13 @@ export interface LedgerState {
|
|
|
9
9
|
export interface OpenedRun {
|
|
10
10
|
task: TaskRecord;
|
|
11
11
|
execution: ExecutionRecord;
|
|
12
|
+
/**
|
|
13
|
+
* How the Host obtains this run's session. Absent or `session` launches a
|
|
14
|
+
* fresh (or reused) execution session; `teammate` means the run belongs to a
|
|
15
|
+
* team-mode cascade, where the Host asks the root's Lead session to spawn a
|
|
16
|
+
* teammate instead of launching a session of its own.
|
|
17
|
+
*/
|
|
18
|
+
dispatch?: 'session' | 'teammate';
|
|
12
19
|
}
|
|
13
20
|
/** Minimal value copy used by the Host session monitor. */
|
|
14
21
|
export interface OpenExecutionReference {
|
|
@@ -52,6 +59,16 @@ export type PowerShellProbe = (script: string, timeoutMs: number) => string | un
|
|
|
52
59
|
* probe is injectable so the fallback chain is testable off Windows.
|
|
53
60
|
*/
|
|
54
61
|
export declare function win32StartTimeMs(pid: number, probe?: PowerShellProbe): number | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Repair parent links after a load or an import: a link whose parent row is
|
|
64
|
+
* missing, cyclic, or names an archived parent while the child is on board is
|
|
65
|
+
* dropped (the task becomes a root) instead of dropping the task. A chain
|
|
66
|
+
* deeper than the current `maxSubtaskDepth` is deliberately KEPT: the limit
|
|
67
|
+
* gates new links, it never deletes a stored one, and every cascade walk is
|
|
68
|
+
* bounded by the limit anyway. The Host is the only writer of these links, so
|
|
69
|
+
* this is a last-resort guard against a hand-edited or imported document.
|
|
70
|
+
*/
|
|
71
|
+
export declare function repairParentLinks(tasks: readonly TaskRecord[]): TaskRecord[];
|
|
55
72
|
export declare class HostTaskLedger {
|
|
56
73
|
private readonly now;
|
|
57
74
|
private document;
|
|
@@ -65,9 +82,24 @@ export declare class HostTaskLedger {
|
|
|
65
82
|
readonly schedulerFile: string;
|
|
66
83
|
/** Session-default permission the confirmation gate compares against. */
|
|
67
84
|
readonly sessionDefaultPermission: TaskPermission;
|
|
85
|
+
/**
|
|
86
|
+
* Deployment subtask depth limit (1..3): the lineage gate every write obeys.
|
|
87
|
+
* The settings card edits it live, so {@link setMaxSubtaskDepth} mutates it
|
|
88
|
+
* instead of remounting the row.
|
|
89
|
+
*/
|
|
90
|
+
private depthLimit;
|
|
68
91
|
constructor(dir?: string, now?: () => number, options?: {
|
|
69
92
|
sessionDefaultPermission?: TaskPermission;
|
|
93
|
+
maxSubtaskDepth?: number;
|
|
70
94
|
});
|
|
95
|
+
/** Current subtask depth limit (1..3). */
|
|
96
|
+
get maxSubtaskDepth(): number;
|
|
97
|
+
/**
|
|
98
|
+
* Apply a live settings edit of the subtask depth limit. A no-op when the
|
|
99
|
+
* normalized limit is unchanged, so a coarse volatile invalidation that
|
|
100
|
+
* changed nothing emits nothing.
|
|
101
|
+
*/
|
|
102
|
+
setMaxSubtaskDepth(depth: number): void;
|
|
71
103
|
/** Remove leftover *.tmp-* files from previous crashes or interrupted writes. */
|
|
72
104
|
private cleanStaleTemporaryFiles;
|
|
73
105
|
/** Revision + scheduler without any task cloning; feeds the SSE event frame. */
|
|
@@ -90,14 +122,66 @@ export declare class HostTaskLedger {
|
|
|
90
122
|
dispose(): void;
|
|
91
123
|
applyRequest(requestId: string, action: TaskBoardAction, initiator?: string): {
|
|
92
124
|
state: LedgerState;
|
|
93
|
-
|
|
125
|
+
runs?: OpenedRun[];
|
|
94
126
|
};
|
|
95
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Open the cascade one due schedule triggers: an empty array means nothing
|
|
129
|
+
* ran (already running, or a participant whose elevated permission is still
|
|
130
|
+
* unconfirmed), and the rule rolls to its next occurrence either way.
|
|
131
|
+
*/
|
|
132
|
+
openScheduled(taskId: string, nextRunAt: number | undefined, triggeredAt: number): OpenedRun[];
|
|
96
133
|
skipMissed(now: number): void;
|
|
97
134
|
setScheduler(patch: Partial<TaskBoardSchedulerSnapshot>): void;
|
|
135
|
+
/**
|
|
136
|
+
* Settle one execution. A cascade participant first records its OWN outcome
|
|
137
|
+
* and only finalizes once every child execution in its run group has
|
|
138
|
+
* settled, so a parent card leaves 'running' with the whole tree's verdict
|
|
139
|
+
* rather than its own turn alone.
|
|
140
|
+
*/
|
|
141
|
+
settle(taskId: string, executionId: string, outcome: ExecutionOutcome, error?: string): void;
|
|
98
142
|
attachSession(taskId: string, executionId: string, sessionId: string): void;
|
|
99
|
-
settle(taskId: string, executionId: string, outcome: 'succeeded' | 'failed' | 'cancelled', error?: string): void;
|
|
100
143
|
private apply;
|
|
144
|
+
/**
|
|
145
|
+
* The binding that makes a run illegal, if any.
|
|
146
|
+
*
|
|
147
|
+
* A plain cascade launches one session per participant, so every participant
|
|
148
|
+
* carries its own resolved binding and each one gates the run. A team run
|
|
149
|
+
* launches only the Lead session: the Lead's binding gates it, while a
|
|
150
|
+
* subtask's OWN above-default pin cannot be applied to a teammate and is
|
|
151
|
+
* refused instead of being silently dropped (an inherited binding is the
|
|
152
|
+
* Lead's own and stays allowed once the Lead is confirmed).
|
|
153
|
+
* @param root - the task being run.
|
|
154
|
+
* @returns the first refusal, or undefined when the run may start.
|
|
155
|
+
*/
|
|
156
|
+
private bindingRefusal;
|
|
157
|
+
/**
|
|
158
|
+
* The tasks a cascade from `rootId` would actually open executions for, with
|
|
159
|
+
* their effective execution targets resolved. Archived members never run, and
|
|
160
|
+
* a member that already has an open execution is skipped (a task cannot run
|
|
161
|
+
* twice), so the result is the participant set the launch will use.
|
|
162
|
+
*/
|
|
163
|
+
private cascadeParticipants;
|
|
164
|
+
/** Whether a task or any of its subtasks still has an open execution. */
|
|
165
|
+
private subtreeHasOpenExecution;
|
|
166
|
+
/**
|
|
167
|
+
* Open one execution per cascade participant (the requested task and its
|
|
168
|
+
* on-board descendants, within the depth limit) under a single run group, and
|
|
169
|
+
* commit the ledger once. The returned runs are ordered root-first, so the
|
|
170
|
+
* Host launches the parent before its subtasks.
|
|
171
|
+
* @param root - the task the user ran.
|
|
172
|
+
* @param now - clock instant (ms epoch).
|
|
173
|
+
* @param initiator - the DSH session that asked for the run (audit only).
|
|
174
|
+
* @param rerun - true to reset the root to 'todo' before starting it.
|
|
175
|
+
* @param nextRunAt - when set, the root's schedule also rolls forward.
|
|
176
|
+
*/
|
|
177
|
+
private startCascade;
|
|
178
|
+
/**
|
|
179
|
+
* Finalize every cascade parent that is ready, walking from `taskId` up the
|
|
180
|
+
* lineage: a member settles once its own outcome is recorded and none of its
|
|
181
|
+
* group children is still open, and its verdict folds those children in
|
|
182
|
+
* (failure dominates, then cancellation).
|
|
183
|
+
*/
|
|
184
|
+
private settleCascade;
|
|
101
185
|
private repairSchedules;
|
|
102
186
|
private reconcileInterruptedStarts;
|
|
103
187
|
/**
|
|
@@ -49,7 +49,29 @@ export declare class SessionLaunchError extends Error {
|
|
|
49
49
|
* first, the provenance wrap then encloses the instruction. Plain tasks (no
|
|
50
50
|
* freeze) keep the bare handover preamble + prompt.
|
|
51
51
|
*/
|
|
52
|
-
|
|
52
|
+
/** One other execution member of a run, as named in the launched task's prompt. */
|
|
53
|
+
export interface PromptPeer {
|
|
54
|
+
/** Task id of the member. */
|
|
55
|
+
id: string;
|
|
56
|
+
/** Member title. */
|
|
57
|
+
title: string;
|
|
58
|
+
/** Teammate name when this member runs as a teammate. */
|
|
59
|
+
name?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Execution-shape context appended to the launched task's prompt. */
|
|
62
|
+
export interface PromptContext {
|
|
63
|
+
/** The other members this run opens, excluding the launched task itself. */
|
|
64
|
+
peers?: readonly PromptPeer[];
|
|
65
|
+
/** True when those members run as teammates inside this session (Team Lead). */
|
|
66
|
+
team?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the execution prompt for one task.
|
|
70
|
+
* @param task - the task being launched.
|
|
71
|
+
* @param context - the run's other members, for the execution-shape section.
|
|
72
|
+
* @returns the prompt text.
|
|
73
|
+
*/
|
|
74
|
+
export declare function promptText(task: TaskRecord, context?: PromptContext): string;
|
|
53
75
|
export declare class HostExecutionRunner {
|
|
54
76
|
private readonly gateway;
|
|
55
77
|
private readonly commands?;
|
|
@@ -77,7 +99,23 @@ export declare class HostExecutionRunner {
|
|
|
77
99
|
*/
|
|
78
100
|
launch(task: TaskRecord, options?: {
|
|
79
101
|
reuseSessionId?: string;
|
|
102
|
+
promptContext?: PromptContext;
|
|
80
103
|
}): Promise<string>;
|
|
104
|
+
/**
|
|
105
|
+
* Fail closed when a reused session records a different Agent preset than
|
|
106
|
+
* the one this task pins.
|
|
107
|
+
*
|
|
108
|
+
* The fresh branch asserts the pin by passing `agentPreset` to
|
|
109
|
+
* `session/create`; the reuse branch must not call that method (it would
|
|
110
|
+
* create or re-adopt the very session the board only meant to continue), so
|
|
111
|
+
* the recorded value is read through `session/projections` — the documented
|
|
112
|
+
* read that resolves no Agent — and compared here (issue #1708). A session
|
|
113
|
+
* whose preset cannot be read is not treated as a match: silently continuing
|
|
114
|
+
* under an unknown composition is the failure this guards.
|
|
115
|
+
* @param sessionId - the session this execution continues in.
|
|
116
|
+
* @param pinned - the preset id the task pins, when it pins one.
|
|
117
|
+
*/
|
|
118
|
+
private assertReusedPreset;
|
|
81
119
|
/**
|
|
82
120
|
* Re-assert the pinned execution contract on a session and queue the task
|
|
83
121
|
* prompt. Shared by the fresh-session and reuse paths so both apply exactly
|
|
@@ -4,6 +4,33 @@ import { HostExecutionRunner, type SessionCommandDispatcher, type TaskBoardWorks
|
|
|
4
4
|
import { PowerInhibitor } from './power-inhibitor.ts';
|
|
5
5
|
import { type TaskBoardAction, type TaskBoardEventPayload, type TaskBoardSnapshot } from './protocol.ts';
|
|
6
6
|
import type { TaskPermission } from './core/handover.ts';
|
|
7
|
+
/** One teammate the Host asks the Agent Teams service to spawn for a team run. */
|
|
8
|
+
export interface TeamSpawnInput {
|
|
9
|
+
/** Session id of the run's Team Lead (the root task's execution session). */
|
|
10
|
+
leadSessionId: string;
|
|
11
|
+
/** Immutable lower-kebab-case teammate name, unique inside the Team. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Short description of the delegated responsibility. */
|
|
14
|
+
description: string;
|
|
15
|
+
/** The subtask's execution prompt. */
|
|
16
|
+
prompt: string;
|
|
17
|
+
}
|
|
18
|
+
/** Result of one teammate spawn attempt. */
|
|
19
|
+
export interface TeamSpawnResult {
|
|
20
|
+
/** The teammate's session id when it reached the active edge. */
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** Why the spawn failed: a thrown call, or a member that settled as failed. */
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The Agent Teams capability the Host needs for team-mode runs. The plugin
|
|
27
|
+
* builds it from the optional `agentTeams` service; it is absent when this
|
|
28
|
+
* deployment does not serve that service, in which case a team run is refused
|
|
29
|
+
* instead of silently degrading into a plain cascade.
|
|
30
|
+
*/
|
|
31
|
+
export interface TaskBoardTeamDispatcher {
|
|
32
|
+
spawn(input: TeamSpawnInput): Promise<TeamSpawnResult>;
|
|
33
|
+
}
|
|
7
34
|
export declare class TaskBoardHostService {
|
|
8
35
|
readonly ledger: HostTaskLedger;
|
|
9
36
|
readonly runner: HostExecutionRunner;
|
|
@@ -23,6 +50,7 @@ export declare class TaskBoardHostService {
|
|
|
23
50
|
*/
|
|
24
51
|
private idleSessionIds;
|
|
25
52
|
private preventIdleSleep;
|
|
53
|
+
private readonly team;
|
|
26
54
|
private lastPowerJson;
|
|
27
55
|
private readonly now;
|
|
28
56
|
constructor(gateway: TypertGateway, options?: {
|
|
@@ -32,6 +60,8 @@ export declare class TaskBoardHostService {
|
|
|
32
60
|
commandDispatcher?: SessionCommandDispatcher;
|
|
33
61
|
workspaceRegistry?: TaskBoardWorkspaceRegistry;
|
|
34
62
|
sessionDefaultPermission?: TaskPermission;
|
|
63
|
+
maxSubtaskDepth?: number;
|
|
64
|
+
team?: TaskBoardTeamDispatcher;
|
|
35
65
|
});
|
|
36
66
|
start(): void;
|
|
37
67
|
setConfiguration(active: boolean, preventIdleSleep: boolean): void;
|
|
@@ -42,11 +72,28 @@ export declare class TaskBoardHostService {
|
|
|
42
72
|
apply(requestId: string, action: TaskBoardAction, initiator?: string): TaskBoardSnapshot;
|
|
43
73
|
dispose(): void;
|
|
44
74
|
private launch;
|
|
75
|
+
private scheduleTeammate;
|
|
76
|
+
/**
|
|
77
|
+
* Spawn one teammate inside the Lead session and attach the teammate's
|
|
78
|
+
* session to the subtask execution, so the existing session monitor settles
|
|
79
|
+
* it from the teammate's own turn like any other execution. A spawn that
|
|
80
|
+
* fails settles the subtask as failed immediately, which then folds into the
|
|
81
|
+
* Lead's cascade verdict.
|
|
82
|
+
*/
|
|
83
|
+
private spawnTeammate;
|
|
45
84
|
private pollSessions;
|
|
46
85
|
/** Reuse the session list this poll already fetched: one list RPC per tick, not 1 + E. */
|
|
47
86
|
private reconcileExecutions;
|
|
48
87
|
private tickSchedule;
|
|
49
88
|
private armedSchedules;
|
|
89
|
+
/**
|
|
90
|
+
* Launch one run set. The root goes first and receives the run shape in its
|
|
91
|
+
* prompt (which members this run opens, and how they run); every plain-cascade
|
|
92
|
+
* member then launches on its own so one refused participant cannot hold the
|
|
93
|
+
* others back. A team run's members are spawned inside the root's Lead
|
|
94
|
+
* session instead, once that session exists.
|
|
95
|
+
*/
|
|
96
|
+
private dispatchRuns;
|
|
50
97
|
private scheduleLaunch;
|
|
51
98
|
private schedulePoll;
|
|
52
99
|
private scheduleTick;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,21 +7,25 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Context, Volatile } from '@deepseek-ai/cordis';
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
10
11
|
import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import { type TaskBoardTeamDispatcher } from './host-service.ts';
|
|
11
13
|
import { type TaskPermission } from './core/tasks.ts';
|
|
12
14
|
/** Default environment variable holding the authenticated proxy token. */
|
|
13
15
|
export declare const DEFAULT_PROXY_TOKEN_ENV = "DSH_TASK_BOARD_PROXY_TOKEN";
|
|
14
16
|
export declare const inject: string[];
|
|
15
17
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
16
|
-
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 \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\uFF1BHost \u6743\u5A01\u8D26\u672C\uFF1B\u5173\u95ED\u6D4F\u89C8\u5668\u540E\u4ECD\u7531 Host \u6267\u884C\u548C\u7ED3\u7B97\uFF1B\u4EFB\u52A1\u53EF\u9489\u4F4F\u5DE5\u4F5C\u533A\u3001agent \u9884\u8BBE\u548C\u6743\u9650\uFF1B\u652F\u6301 Host \u672C\u5730\u65F6\u533A\u7684 5 \u6BB5 cron\uFF0C\u9519\u8FC7\u7684\u89E6\u53D1\u70B9\u4E0D\u8865\u8DD1\uFF1B\u53EF\u9009\u4E14\u9ED8\u8BA4\u5173\u95ED\u7684\u7A7A\u95F2\u7CFB\u7EDF\u7761\u7720\u4FDD\u62A4\u5141\u8BB8\u5C4F\u5E55\u7184\u706D\uFF0C\u4F46\u4E0D\u627F\u8BFA\u62E6\u622A\u5408\u76D6\u3001\u624B\u52A8\u7761\u7720\u3001\u4F11\u7720\u3001\u5173\u673A\u6216\u5524\u9192\u5DF2\u7761\u7720\u673A\u5668\u3002\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\u82E5\u4F60\u540C\u65F6\u7528 todo_write \u7EF4\u62A4\u4F1A\u8BDD\u9876\u90E8\u7684\u53EF\u89C1\u8BA1\u5212\u5217\u8868\uFF0C\u6700\u7EC8\u56DE\u590D\u524D\u5FC5\u987B\u518D\u6B21\u8C03\u7528 todo_write \u6536\u5C3E\uFF1A\u6CA1\u6709\u5269\u4F59\u5DE5\u4F5C\u65F6\u4E0D\u8981\u4FDD\u7559 in_progress\uFF0C\u5DF2\u5B8C\u6210\u7684\u6700\u540E\u4E00\u6B65\u8981\u6807\u4E3A completed\u3002";
|
|
18
|
+
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 \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\uFF1BHost \u6743\u5A01\u8D26\u672C\uFF1B\u5173\u95ED\u6D4F\u89C8\u5668\u540E\u4ECD\u7531 Host \u6267\u884C\u548C\u7ED3\u7B97\uFF1B\u4EFB\u52A1\u53EF\u9489\u4F4F\u5DE5\u4F5C\u533A\u3001agent \u9884\u8BBE\u548C\u6743\u9650\uFF1B\u4EFB\u52A1\u53EF\u5EFA\u5B50\u4EFB\u52A1\uFF08\u6DF1\u5EA6\u4E0A\u9650\u53EF\u914D 1..3\uFF0C\u9ED8\u8BA4 1 \u5C42\uFF0C\u5B50\u4EFB\u52A1\u4E0D\u80FD\u518D\u5E26\u5B50\u4EFB\u52A1\uFF09\uFF0C\u6267\u884C\u7236\u4EFB\u52A1\u4F1A\u5E76\u53D1\u6267\u884C\u5176\u5B50\u4EFB\u52A1\u6811\uFF0C\u5B50\u4EFB\u52A1\u53EF\u5355\u72EC\u8986\u76D6\u6743\u9650\u4E0E\u6A21\u578B\uFF1B\u53E6\u6CE8\u518C task_board_* agent \u5DE5\u5177\uFF08list/get/create/update/set_parent/run/manage/schedule\uFF09\uFF0C\u4EFB\u4F55\u4F1A\u8BDD\u90FD\u53EF\u76F4\u63A5\u8BFB\u5199\u770B\u677F\u3001\u5B50\u4EFB\u52A1\u4E0E\u5B9A\u65F6\u8BA1\u5212\uFF0C\u4F46\u8FD0\u884C\u4EFB\u52A1\u4F1A\u771F\u5B9E\u6267\u884C\u5E76\u6D88\u8017\u989D\u5EA6\uFF0C\u9AD8\u4E8E\u4F1A\u8BDD\u9ED8\u8BA4\u6743\u9650\u7684\u7ED1\u5B9A\u4ECD\u5FC5\u987B\u7531\u7528\u6237\u5728 GUI \u4EBA\u5DE5\u786E\u8BA4\uFF08\u5DE5\u5177\u523B\u610F\u4E0D\u63D0\u4F9B\u786E\u8BA4\u80FD\u529B\uFF09\uFF1B\u652F\u6301 Host \u672C\u5730\u65F6\u533A\u7684 5 \u6BB5 cron\uFF0C\u9519\u8FC7\u7684\u89E6\u53D1\u70B9\u4E0D\u8865\u8DD1\uFF1B\u53EF\u9009\u4E14\u9ED8\u8BA4\u5173\u95ED\u7684\u7A7A\u95F2\u7CFB\u7EDF\u7761\u7720\u4FDD\u62A4\u5141\u8BB8\u5C4F\u5E55\u7184\u706D\uFF0C\u4F46\u4E0D\u627F\u8BFA\u62E6\u622A\u5408\u76D6\u3001\u624B\u52A8\u7761\u7720\u3001\u4F11\u7720\u3001\u5173\u673A\u6216\u5524\u9192\u5DF2\u7761\u7720\u673A\u5668\u3002\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\u82E5\u4F60\u540C\u65F6\u7528 todo_write \u7EF4\u62A4\u4F1A\u8BDD\u9876\u90E8\u7684\u53EF\u89C1\u8BA1\u5212\u5217\u8868\uFF0C\u6700\u7EC8\u56DE\u590D\u524D\u5FC5\u987B\u518D\u6B21\u8C03\u7528 todo_write \u6536\u5C3E\uFF1A\u6CA1\u6709\u5269\u4F59\u5DE5\u4F5C\u65F6\u4E0D\u8981\u4FDD\u7559 in_progress\uFF0C\u5DF2\u5B8C\u6210\u7684\u6700\u540E\u4E00\u6B65\u8981\u6807\u4E3A completed\u3002";
|
|
17
19
|
/**
|
|
18
20
|
* Plugin config, validated by the same-named schemastery schema.
|
|
19
21
|
*
|
|
20
22
|
* This schema IS the board's settings page: the 0.1.7 Host serves one
|
|
21
23
|
* configuration form per profile entry from the entry's own Config, so the
|
|
22
|
-
*
|
|
24
|
+
* fields the browser card edits are marked volatile — the Loader commits
|
|
23
25
|
* an edit into the running fiber's references without remounting the row (no
|
|
24
|
-
* settings document of the plugin's own exists any more).
|
|
26
|
+
* settings document of the plugin's own exists any more). A field without the
|
|
27
|
+
* marker is refused by the settings write path, which fences writes on
|
|
28
|
+
* volatility.
|
|
25
29
|
*/
|
|
26
30
|
export interface Config {
|
|
27
31
|
/**
|
|
@@ -48,7 +52,25 @@ export interface Config {
|
|
|
48
52
|
* confirmation before it may run; cron refuses unconfirmed cards.
|
|
49
53
|
*/
|
|
50
54
|
sessionDefaultPermission?: TaskPermission;
|
|
55
|
+
/**
|
|
56
|
+
* Subtask depth limit, 1..3 (default 1). At 1 a task may carry one level of
|
|
57
|
+
* subtasks and a subtask cannot be given subtasks of its own; raising it
|
|
58
|
+
* allows deeper trees. Every extra level multiplies the sessions one run
|
|
59
|
+
* of the root task opens, so the ceiling is deliberately small.
|
|
60
|
+
*/
|
|
61
|
+
maxSubtaskDepth?: Volatile<number>;
|
|
62
|
+
/**
|
|
63
|
+
* Continuable-subagent provider the Agent Teams service uses to compose a
|
|
64
|
+
* teammate. Matches the Agent Teams tool plugin's `freshProvider` default;
|
|
65
|
+
* only team-mode runs use it.
|
|
66
|
+
*/
|
|
67
|
+
teamProvider?: string;
|
|
51
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Continuable-subagent provider for fresh teammates, matching the Agent Teams
|
|
71
|
+
* tool plugin's own `freshProvider` default.
|
|
72
|
+
*/
|
|
73
|
+
export declare const DEFAULT_TEAM_PROVIDER = "spawn";
|
|
52
74
|
/**
|
|
53
75
|
* The schema is left to inference rather than annotated with `z<Config>`: a
|
|
54
76
|
* volatile field's parsed output is a `Volatile` reference while its accepted
|
|
@@ -62,6 +84,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
62
84
|
trustedProxyHosts: z<string[], string[], "defined">;
|
|
63
85
|
proxyTokenEnv: z<string, string, "defined">;
|
|
64
86
|
sessionDefaultPermission: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access", "defined">;
|
|
87
|
+
maxSubtaskDepth: z<number, number, "volatile-defined">;
|
|
88
|
+
teamProvider: z<string, string, "defined">;
|
|
65
89
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
66
90
|
announceToAgent: z<boolean, boolean, "volatile-defined">;
|
|
67
91
|
enabled: z<boolean, boolean, "volatile-defined">;
|
|
@@ -69,6 +93,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
69
93
|
trustedProxyHosts: z<string[], string[], "defined">;
|
|
70
94
|
proxyTokenEnv: z<string, string, "defined">;
|
|
71
95
|
sessionDefaultPermission: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access", "defined">;
|
|
96
|
+
maxSubtaskDepth: z<number, number, "volatile-defined">;
|
|
97
|
+
teamProvider: z<string, string, "defined">;
|
|
72
98
|
}>>, "plain">;
|
|
73
99
|
declare module '@deepseek-ai/cordis' {
|
|
74
100
|
interface Events {
|
|
@@ -100,6 +126,58 @@ export declare function resolveProxyAccess(config: Config | undefined, env?: Nod
|
|
|
100
126
|
trustedProxyHosts: string[];
|
|
101
127
|
proxyToken?: string;
|
|
102
128
|
};
|
|
129
|
+
/** The registry face the agent tools register into. */
|
|
130
|
+
interface AgentToolRegistry {
|
|
131
|
+
register(definition: ToolDefinition): () => void;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve the optional agent-tool registry. The board deliberately does not
|
|
135
|
+
* inject it: a deployment whose runtime serves no registry must still mount the
|
|
136
|
+
* whole board and lose only the agent-tool surface, the same tolerance the
|
|
137
|
+
* optional llm service gets.
|
|
138
|
+
* @param ctx - the plugin context.
|
|
139
|
+
* @returns the registry, or undefined when this deployment serves none.
|
|
140
|
+
*/
|
|
141
|
+
export declare function resolveToolRegistry(ctx: Context): AgentToolRegistry | undefined;
|
|
142
|
+
/** The subset of the Agent Teams service this plugin drives. */
|
|
143
|
+
interface AgentTeamsFace {
|
|
144
|
+
spawnTeammate(caller: unknown, request: {
|
|
145
|
+
name: string;
|
|
146
|
+
description: string;
|
|
147
|
+
prompt: Array<{
|
|
148
|
+
type: 'text';
|
|
149
|
+
text: string;
|
|
150
|
+
}>;
|
|
151
|
+
context: 'fresh' | 'fork';
|
|
152
|
+
provider: string;
|
|
153
|
+
signal: AbortSignal;
|
|
154
|
+
}): Promise<{
|
|
155
|
+
member?: {
|
|
156
|
+
id?: string;
|
|
157
|
+
phase?: string;
|
|
158
|
+
error?: string;
|
|
159
|
+
};
|
|
160
|
+
}>;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the optional Agent Teams service. Like the tool registry and the llm
|
|
164
|
+
* runtime, it is deliberately not injected: a deployment without Agent Teams
|
|
165
|
+
* must still mount the whole board and only lose team-mode runs.
|
|
166
|
+
* @param ctx - the plugin context.
|
|
167
|
+
* @returns the service, or undefined when this deployment serves none.
|
|
168
|
+
*/
|
|
169
|
+
export declare function resolveAgentTeams(ctx: Context): AgentTeamsFace | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Build the team dispatcher: the run's Lead session spawns one teammate per
|
|
172
|
+
* subtask through the Agent Teams service, and the teammate's session id is
|
|
173
|
+
* attached to that subtask's execution so the ordinary session monitor settles
|
|
174
|
+
* it. Returns undefined when the deployment serves no Agent Teams service or
|
|
175
|
+
* no live-agent registry, which makes a team run fail closed.
|
|
176
|
+
* @param ctx - the plugin context.
|
|
177
|
+
* @param provider - the continuable-subagent provider for fresh teammates.
|
|
178
|
+
* @returns the dispatcher, or undefined when team runs cannot be served.
|
|
179
|
+
*/
|
|
180
|
+
export declare function buildTeamDispatcher(ctx: Context, provider: string): TaskBoardTeamDispatcher | undefined;
|
|
103
181
|
/**
|
|
104
182
|
* Read the optional `llm` service. The board deliberately does not inject it:
|
|
105
183
|
* a deployment without a model must still mount the board, and the parse route
|
|
@@ -112,8 +190,8 @@ export declare function resolveLlmRuntime(ctx: Context): LlmRuntime | undefined;
|
|
|
112
190
|
* Activate the board's host half: ledger, routes, cron scheduler, power
|
|
113
191
|
* inhibitor, and the model-facing announcement.
|
|
114
192
|
*
|
|
115
|
-
* The effective settings are the config the Host hands this row. The
|
|
116
|
-
*
|
|
193
|
+
* The effective settings are the config the Host hands this row. The fields
|
|
194
|
+
* the browser card edits are schema-volatile, so the Loader commits an
|
|
117
195
|
* edit into the running fiber's references instead of remounting the row —
|
|
118
196
|
* `sync` therefore reads them at use time and follows
|
|
119
197
|
* `loader/volatile-update`, the event the Loader emits once it has committed
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -29,6 +29,16 @@ export interface TaskBoardSnapshot {
|
|
|
29
29
|
power: TaskBoardPowerSnapshot;
|
|
30
30
|
/** Session-default permission the confirmation gate compares against. */
|
|
31
31
|
sessionDefaultPermission?: TaskPermission;
|
|
32
|
+
/**
|
|
33
|
+
* Deployment subtask depth limit (1..3). The browser mirrors it to enable or
|
|
34
|
+
* disable the subtask controls; the Host ledger is the authority.
|
|
35
|
+
*/
|
|
36
|
+
maxSubtaskDepth?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Whether this deployment serves the Agent Teams service, so a task may opt
|
|
39
|
+
* into team execution (Team Lead session plus one teammate per subtask).
|
|
40
|
+
*/
|
|
41
|
+
teamRunAvailable?: boolean;
|
|
32
42
|
}
|
|
33
43
|
/** SSE event frame: revision/scheduler/power only, never the task list. */
|
|
34
44
|
export interface TaskBoardEventPayload {
|
|
@@ -96,6 +106,10 @@ export type TaskBoardAction = {
|
|
|
96
106
|
} | {
|
|
97
107
|
kind: 'confirm-permission';
|
|
98
108
|
taskId: string;
|
|
109
|
+
} | {
|
|
110
|
+
kind: 'set-parent';
|
|
111
|
+
taskId: string;
|
|
112
|
+
parentId: string | null;
|
|
99
113
|
};
|
|
100
114
|
export interface TaskBoardActionEnvelope {
|
|
101
115
|
requestId: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Host-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dsh": {
|
|
26
26
|
"engines": {
|
|
27
|
-
"dsh": ">=0.1.7-rc.
|
|
27
|
+
"dsh": ">=0.1.7-rc.2"
|
|
28
28
|
},
|
|
29
29
|
"bundle": {
|
|
30
30
|
"patch": "./cordis.patch.yml"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh": ">=0.1.7-rc.
|
|
46
|
+
"@deepseek-ai/dsh": ">=0.1.7-rc.2",
|
|
47
47
|
"react": "^18.2.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
@@ -51,24 +51,26 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
54
|
-
"@deepseek-ai/dsh-agent": "^0.1.7-rc.
|
|
55
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.
|
|
56
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.7-rc.
|
|
57
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.
|
|
58
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.
|
|
59
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.7-rc.
|
|
60
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.7-rc.
|
|
61
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.7-rc.
|
|
62
|
-
"@deepseek-ai/dsh-client-store": "^0.1.7-rc.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.7-rc.
|
|
67
|
-
"@deepseek-ai/dsh-commands": "^0.1.7-rc.
|
|
68
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
54
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.7-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.7-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.7-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.7-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-commands": "^0.1.7-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-workspace": "^0.1.7-rc.2",
|
|
72
74
|
"@types/node": "^22.20.0",
|
|
73
75
|
"@types/react": "~18.3.1",
|
|
74
76
|
"@types/react-dom": "^18.3.5",
|
|
@@ -9,8 +9,33 @@ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
|
9
9
|
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
10
10
|
import { useEffect, useState } from 'react'
|
|
11
11
|
import type { TaskBoardPowerSnapshot } from '../protocol.ts'
|
|
12
|
-
import { PluginSettingsCard, BooleanField } from './PluginSettingsCard.tsx'
|
|
13
|
-
import {
|
|
12
|
+
import { PluginSettingsCard, BooleanField, ChoiceField } from './PluginSettingsCard.tsx'
|
|
13
|
+
import { SUBTASK_DEPTH_MAX, SUBTASK_DEPTH_MIN } from '../core/subtask.ts'
|
|
14
|
+
import { CardForm, booleanField, type CardActions, type CardShell, type FieldSpec, type FieldState as CardFieldState } from './settings-form.ts'
|
|
15
|
+
|
|
16
|
+
/** The depth choices the card offers, derived from the supported range. */
|
|
17
|
+
const SUBTASK_DEPTH_CHOICES: readonly string[] = Array.from(
|
|
18
|
+
{ length: SUBTASK_DEPTH_MAX - SUBTASK_DEPTH_MIN + 1 },
|
|
19
|
+
(_, index) => String(SUBTASK_DEPTH_MIN + index),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The depth field: a choice among the supported levels whose draft text is a
|
|
24
|
+
* number, because the Host schema (`maxSubtaskDepth`) is numeric. A draft
|
|
25
|
+
* outside the range blocks the save instead of staging a value the Host
|
|
26
|
+
* refuses.
|
|
27
|
+
*/
|
|
28
|
+
function subtaskDepthField(): FieldSpec {
|
|
29
|
+
return {
|
|
30
|
+
field: 'maxSubtaskDepth',
|
|
31
|
+
format: value => typeof value === 'number' && Number.isInteger(value) ? String(value) : '',
|
|
32
|
+
parse: (text) => {
|
|
33
|
+
const trimmed = text.trim()
|
|
34
|
+
if (trimmed === '') return { kind: 'clear' }
|
|
35
|
+
return SUBTASK_DEPTH_CHOICES.includes(trimmed) ? { kind: 'set', value: Number(trimmed) } : undefined
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
14
39
|
|
|
15
40
|
/** The task-board fields this card edits (the namespace's full schema). */
|
|
16
41
|
export interface TaskBoardSettings {
|
|
@@ -20,6 +45,8 @@ export interface TaskBoardSettings {
|
|
|
20
45
|
announceToAgent?: boolean
|
|
21
46
|
/** Prevent host idle sleep while sessions run or schedules are armed. */
|
|
22
47
|
preventIdleSleep?: boolean
|
|
48
|
+
/** Subtask depth limit (1..3); 1 means a single level of subtasks. */
|
|
49
|
+
maxSubtaskDepth?: number
|
|
23
50
|
}
|
|
24
51
|
|
|
25
52
|
/** What the task-board card renders. */
|
|
@@ -30,6 +57,8 @@ export interface TaskBoardSettingsCardState extends CardShell {
|
|
|
30
57
|
announceToAgent: CardFieldState
|
|
31
58
|
/** Idle-system-sleep protection flag. */
|
|
32
59
|
preventIdleSleep: CardFieldState
|
|
60
|
+
/** Subtask depth limit field. */
|
|
61
|
+
maxSubtaskDepth: CardFieldState
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
/** The registration-side face the card's slot entry injects. */
|
|
@@ -51,6 +80,7 @@ export class TaskBoardSettingsCardController {
|
|
|
51
80
|
booleanField('enabled'),
|
|
52
81
|
booleanField('announceToAgent'),
|
|
53
82
|
booleanField('preventIdleSleep'),
|
|
83
|
+
subtaskDepthField(),
|
|
54
84
|
])
|
|
55
85
|
this.store = this.form.bind(() => this.projection())
|
|
56
86
|
}
|
|
@@ -61,6 +91,7 @@ export class TaskBoardSettingsCardController {
|
|
|
61
91
|
enabled: this.form.field('enabled'),
|
|
62
92
|
announceToAgent: this.form.field('announceToAgent'),
|
|
63
93
|
preventIdleSleep: this.form.field('preventIdleSleep'),
|
|
94
|
+
maxSubtaskDepth: this.form.field('maxSubtaskDepth'),
|
|
64
95
|
}
|
|
65
96
|
}
|
|
66
97
|
|
|
@@ -102,7 +133,7 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
102
133
|
// one frame on subscribe; polling the full /state snapshot every 5 s
|
|
103
134
|
// re-cloned and re-serialized the whole ledger server-side for one field.
|
|
104
135
|
let live = true
|
|
105
|
-
const events = new EventSource('
|
|
136
|
+
const events = new EventSource('api/task-board/events')
|
|
106
137
|
events.onmessage = (message: MessageEvent<string>): void => {
|
|
107
138
|
try {
|
|
108
139
|
const frame = JSON.parse(message.data) as { power?: TaskBoardPowerSnapshot }
|
|
@@ -167,6 +198,20 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
167
198
|
onEdit={(text) => { props.edit('preventIdleSleep', text) }}
|
|
168
199
|
onReset={() => { props.resetField('preventIdleSleep') }}
|
|
169
200
|
/>
|
|
201
|
+
<ChoiceField
|
|
202
|
+
id="settings-task-board-subtask-depth"
|
|
203
|
+
label={t('settings.maxSubtaskDepth')}
|
|
204
|
+
hint={t('settings.maxSubtaskDepthHint')}
|
|
205
|
+
inheritLabel={t('settings.inherit')}
|
|
206
|
+
choices={SUBTASK_DEPTH_CHOICES.map(value => ({
|
|
207
|
+
value,
|
|
208
|
+
label: t('settings.maxSubtaskDepthOption', { depth: value }),
|
|
209
|
+
}))}
|
|
210
|
+
{...fieldProps}
|
|
211
|
+
{...state.maxSubtaskDepth}
|
|
212
|
+
onEdit={(text) => { props.edit('maxSubtaskDepth', text) }}
|
|
213
|
+
onReset={() => { props.resetField('maxSubtaskDepth') }}
|
|
214
|
+
/>
|
|
170
215
|
<p>
|
|
171
216
|
{t('settings.powerStatus', {
|
|
172
217
|
platform: power?.platform ?? t('settings.powerUnknown'),
|