@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.
- package/README.i18n.yaml +2 -2
- package/README.md +82 -104
- package/README.zh.md +82 -104
- package/lib/client.js +574 -581
- package/lib/client.js.map +1 -1
- package/lib/index.js +2144 -5
- package/lib/types/client/PluginSettingsCard.d.ts +7 -1
- package/lib/types/client/TaskBoardSettingsCard.d.ts +7 -4
- package/lib/types/client/board/TaskCard.d.ts +5 -2
- package/lib/types/client/host-api.d.ts +18 -0
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/core/controller.d.ts +40 -15
- package/lib/types/core/schedule.d.ts +4 -2
- package/lib/types/core/tasks.d.ts +6 -6
- package/lib/types/core/use-cases/task-archive.d.ts +5 -4
- package/lib/types/core/use-cases/task-schedule.d.ts +3 -3
- package/lib/types/dsh-home.d.ts +16 -0
- package/lib/types/host-ledger.d.ts +42 -0
- package/lib/types/host-routes.d.ts +24 -0
- package/lib/types/host-runner.d.ts +30 -0
- package/lib/types/host-service.d.ts +41 -0
- package/lib/types/index.d.ts +17 -9
- package/lib/types/loopback.d.ts +24 -0
- package/lib/types/power-inhibitor.d.ts +73 -0
- package/lib/types/protocol.d.ts +72 -0
- package/package.json +12 -9
- package/src/client/PluginSettingsCard.tsx +10 -0
- package/src/client/TaskBoardSettingsCard.tsx +51 -4
- package/src/client/board/NewTaskModal.tsx +9 -6
- package/src/client/board/TaskBoard.tsx +21 -4
- package/src/client/board/TaskCard.tsx +24 -8
- package/src/client/board/TaskDetail.tsx +83 -50
- package/src/client/host-api.ts +95 -0
- package/src/client/index.ts +9 -78
- package/src/client/locales.ts +24 -4
- package/src/core/controller.ts +205 -24
- package/src/core/schedule.ts +20 -3
- package/src/core/store.ts +4 -8
- package/src/core/tasks.ts +6 -6
- package/src/core/use-cases/task-archive.ts +14 -5
- package/src/core/use-cases/task-schedule.ts +6 -5
- package/src/dsh-home.ts +36 -0
- package/src/host-ledger.ts +478 -0
- package/src/host-routes.ts +188 -0
- package/src/host-runner.ts +134 -0
- package/src/host-service.ts +209 -0
- package/src/index.ts +54 -11
- package/src/loopback.ts +63 -0
- package/src/power-inhibitor.ts +321 -0
- package/src/protocol.ts +213 -0
- package/lib/types/core/scheduler.d.ts +0 -70
- package/src/core/scheduler.ts +0 -161
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { type ReactNode } from 'react';
|
|
10
10
|
import type { CardShell } from './settings-form.ts';
|
|
11
11
|
/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
|
|
12
|
-
export declare const CARD_COPY_KEYS: readonly [
|
|
12
|
+
export declare const CARD_COPY_KEYS: readonly ["settings.collapse", "settings.expand", "settings.notExposed", "settings.unsaved", "settings.readOnly", "settings.saveFailed", "settings.discard", "settings.save", "settings.saving"];
|
|
13
13
|
/** Copy key the card chrome itself reads. */
|
|
14
14
|
export type CardCopyKey = (typeof CARD_COPY_KEYS)[number];
|
|
15
15
|
/** Key domain for the plugin's own copy (inferred per consumer). */
|
|
@@ -39,6 +39,12 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
|
39
39
|
* already provides the selection.
|
|
40
40
|
*/
|
|
41
41
|
alwaysOpen?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Hide the save/discard footer: cards whose body applies its own changes
|
|
44
|
+
* immediately (an embedded external settings section) have no staged
|
|
45
|
+
* edits, so the footer would sit there permanently disabled.
|
|
46
|
+
*/
|
|
47
|
+
hideFooter?: boolean;
|
|
42
48
|
/** The plugin's controls. */
|
|
43
49
|
children: ReactNode;
|
|
44
50
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* plugin-configuration section renders, bound to the `task-board`
|
|
5
|
-
* namespace.
|
|
2
|
+
* Task-board settings for availability, agent announcement, and optional Host
|
|
3
|
+
* idle-sleep protection. Registers into the `settings.plugin.item` slot the
|
|
4
|
+
* plugin-configuration section renders, bound to the `task-board` namespace.
|
|
6
5
|
*/
|
|
7
6
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
8
7
|
import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
@@ -13,6 +12,8 @@ export interface TaskBoardSettings {
|
|
|
13
12
|
enabled?: boolean;
|
|
14
13
|
/** Whether the board announces itself in every agent's system prompt. */
|
|
15
14
|
announceToAgent?: boolean;
|
|
15
|
+
/** Prevent host idle sleep while sessions run or schedules are armed. */
|
|
16
|
+
preventIdleSleep?: boolean;
|
|
16
17
|
}
|
|
17
18
|
/** What the task-board card renders. */
|
|
18
19
|
export interface TaskBoardSettingsCardState extends CardShell {
|
|
@@ -20,6 +21,8 @@ export interface TaskBoardSettingsCardState extends CardShell {
|
|
|
20
21
|
enabled: CardFieldState;
|
|
21
22
|
/** System-prompt announcement flag. */
|
|
22
23
|
announceToAgent: CardFieldState;
|
|
24
|
+
/** Idle-system-sleep protection flag. */
|
|
25
|
+
preventIdleSleep: CardFieldState;
|
|
23
26
|
}
|
|
24
27
|
/** The registration-side face the card's slot entry injects. */
|
|
25
28
|
export interface TaskBoardSettingsCardFace extends CardActions {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { TaskRecord } from '../../core/tasks.ts';
|
|
2
2
|
/** Compact relative/absolute time label. */
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function formatHostTimestamp(ms: number, timeZone?: string): string;
|
|
4
|
+
export declare function formatTime(ms: number, timeZone?: string): string;
|
|
4
5
|
/** One card in a column. */
|
|
5
|
-
declare function TaskCardInner({ task, onClick }: {
|
|
6
|
+
declare function TaskCardInner({ task, pending, timeZone, onClick }: {
|
|
6
7
|
task: TaskRecord;
|
|
8
|
+
pending: boolean;
|
|
9
|
+
timeZone?: string;
|
|
7
10
|
onClick: () => void;
|
|
8
11
|
}): import("react").JSX.Element;
|
|
9
12
|
/** Memoized card: re-renders only when the card's own task record changes. */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TaskRecord } from '../core/tasks.ts';
|
|
2
|
+
import { type TaskBoardAction, type TaskBoardSnapshot } from '../protocol.ts';
|
|
3
|
+
export interface TaskBoardHostTransport {
|
|
4
|
+
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
5
|
+
state(): Promise<TaskBoardSnapshot>;
|
|
6
|
+
action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
|
|
10
|
+
private readonly storage;
|
|
11
|
+
constructor(storage?: Pick<Storage, 'getItem' | 'setItem'> | undefined);
|
|
12
|
+
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
13
|
+
state(): Promise<TaskBoardSnapshot>;
|
|
14
|
+
action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
|
|
15
|
+
private post;
|
|
16
|
+
private request;
|
|
17
|
+
subscribe(listener: () => void): () => void;
|
|
18
|
+
}
|
|
@@ -24,8 +24,12 @@ export declare const zh: {
|
|
|
24
24
|
'board.status.done': string;
|
|
25
25
|
'board.status.failed': string;
|
|
26
26
|
'board.runs': string;
|
|
27
|
+
'board.pending': string;
|
|
27
28
|
'board.updated': string;
|
|
28
29
|
'board.created': string;
|
|
30
|
+
'board.hostError': string;
|
|
31
|
+
'board.retryHost': string;
|
|
32
|
+
'board.hostMeta': string;
|
|
29
33
|
'new.title': string;
|
|
30
34
|
'new.titlePlaceholder': string;
|
|
31
35
|
'new.description': string;
|
|
@@ -99,6 +103,12 @@ export declare const zh: {
|
|
|
99
103
|
'settings.enabledHint': string;
|
|
100
104
|
'settings.announceToAgent': string;
|
|
101
105
|
'settings.announceToAgentHint': string;
|
|
106
|
+
'settings.preventIdleSleep': string;
|
|
107
|
+
'settings.preventIdleSleepHint': string;
|
|
108
|
+
'settings.powerStatus': string;
|
|
109
|
+
'settings.powerBoundary': string;
|
|
110
|
+
'settings.powerUnknown': string;
|
|
111
|
+
'settings.powerError': string;
|
|
102
112
|
'settings.inherit': string;
|
|
103
113
|
'settings.on': string;
|
|
104
114
|
'settings.off': string;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Board controller: the single owner of task-ledger state and view state.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* navigates to a session (the sessions-list `current` selection changes).
|
|
4
|
+
* In production it projects the Host ledger and submits confirmed actions;
|
|
5
|
+
* the legacy store/execution seams remain for v1 migration tests. It also
|
|
6
|
+
* closes the board whenever the user navigates to a session.
|
|
8
7
|
* Framework-free (structural runtime faces) so the whole orchestration is
|
|
9
8
|
* unit-testable with fakes.
|
|
10
9
|
*
|
|
@@ -13,10 +12,17 @@
|
|
|
13
12
|
* owns only the orchestration seam (state, persistence, notify, execution,
|
|
14
13
|
* navigation, reconciliation).
|
|
15
14
|
*/
|
|
16
|
-
import { ExecutionService } from './execution.ts';
|
|
15
|
+
import type { ExecutionService } from './execution.ts';
|
|
17
16
|
import type { TaskStore } from './store.ts';
|
|
18
17
|
import { type NewTaskInput, type TaskRecord, type TaskStatus } from './tasks.ts';
|
|
19
18
|
import { type TaskUpdatePatch } from './use-cases/task-update.ts';
|
|
19
|
+
import type { TaskBoardAction, TaskBoardSnapshot } from '../protocol.ts';
|
|
20
|
+
export interface TaskBoardTransport {
|
|
21
|
+
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
22
|
+
state(): Promise<TaskBoardSnapshot>;
|
|
23
|
+
action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
|
|
24
|
+
subscribe(listener: () => void): () => void;
|
|
25
|
+
}
|
|
20
26
|
/** The sessions face the controller needs for navigation awareness. */
|
|
21
27
|
export interface SessionsControllerFace {
|
|
22
28
|
list: {
|
|
@@ -31,7 +37,8 @@ export interface SessionsControllerFace {
|
|
|
31
37
|
/** Controller dependencies (all swappable in tests). */
|
|
32
38
|
export interface ControllerDeps {
|
|
33
39
|
store: TaskStore;
|
|
34
|
-
|
|
40
|
+
/** Legacy browser execution seam used only by isolated v1 tests. */
|
|
41
|
+
exec?: ExecutionService;
|
|
35
42
|
sessions: SessionsControllerFace;
|
|
36
43
|
/** Clock; defaults to Date.now. */
|
|
37
44
|
now?: () => number;
|
|
@@ -39,6 +46,8 @@ export interface ControllerDeps {
|
|
|
39
46
|
uuid?: () => string;
|
|
40
47
|
/** Debounce (ms) for session-list-changed reconciles; defaults to 350. */
|
|
41
48
|
reconcileDebounceMs?: number;
|
|
49
|
+
/** Host-authoritative transport; absent keeps the legacy in-memory test path. */
|
|
50
|
+
transport?: TaskBoardTransport;
|
|
42
51
|
}
|
|
43
52
|
/** One workspace option the execution-target pickers offer. */
|
|
44
53
|
export interface ExecutionWorkspaceOption {
|
|
@@ -69,6 +78,9 @@ export interface ControllerSnapshot {
|
|
|
69
78
|
selectedTaskId: string | undefined;
|
|
70
79
|
/** Picker option sets (workspace list + agent-preset roster). */
|
|
71
80
|
executionOptions: ExecutionOptionsSnapshot;
|
|
81
|
+
pendingTaskIds: readonly string[];
|
|
82
|
+
transportError?: string;
|
|
83
|
+
host?: Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power'>;
|
|
72
84
|
}
|
|
73
85
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
74
86
|
export declare function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined;
|
|
@@ -87,6 +99,12 @@ export declare class BoardController {
|
|
|
87
99
|
private disposers;
|
|
88
100
|
private readonly now;
|
|
89
101
|
private readonly uuid;
|
|
102
|
+
private readonly pendingTaskIds;
|
|
103
|
+
private readonly taskQueues;
|
|
104
|
+
private transportError;
|
|
105
|
+
private hostState;
|
|
106
|
+
private remoteSubscribed;
|
|
107
|
+
private remoteInitialization;
|
|
90
108
|
/** @param deps - store, execution service, and the sessions navigation face. */
|
|
91
109
|
constructor(deps: ControllerDeps);
|
|
92
110
|
/** Load the persisted ledger and start the navigation/status subscriptions. */
|
|
@@ -95,6 +113,10 @@ export declare class BoardController {
|
|
|
95
113
|
dispose(): void;
|
|
96
114
|
getSnapshot(): ControllerSnapshot;
|
|
97
115
|
subscribe(fn: () => void): () => void;
|
|
116
|
+
/** Whether production mutations are confirmed by the Host transport. */
|
|
117
|
+
isHostBacked(): boolean;
|
|
118
|
+
/** Retry initial migration/state synchronization after an explicit Host error. */
|
|
119
|
+
retryHostSync(): Promise<boolean>;
|
|
98
120
|
openBoard(): void;
|
|
99
121
|
closeBoard(): void;
|
|
100
122
|
toggleBoard(): void;
|
|
@@ -107,6 +129,8 @@ export declare class BoardController {
|
|
|
107
129
|
openTask(id: string): void;
|
|
108
130
|
closeTask(): void;
|
|
109
131
|
createTask(input: NewTaskInput): TaskRecord | undefined;
|
|
132
|
+
/** Create through the Host and expose the task only after confirmation. */
|
|
133
|
+
createTaskConfirmed(input: NewTaskInput): Promise<TaskRecord | undefined>;
|
|
110
134
|
updateTask(id: string, patch: TaskUpdatePatch): void;
|
|
111
135
|
/**
|
|
112
136
|
* Replace (a part of) the picker option sets the UI feeds (workspace list
|
|
@@ -138,18 +162,13 @@ export declare class BoardController {
|
|
|
138
162
|
cron?: string;
|
|
139
163
|
}): boolean;
|
|
140
164
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* schedule rule (it was deleted mid-tick, for example).
|
|
165
|
+
* Legacy pure-controller seam retained for migration-focused tests. The
|
|
166
|
+
* production browser never rolls schedules; the Host ledger owns them.
|
|
144
167
|
*/
|
|
145
168
|
applyScheduleNextRun(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void;
|
|
146
169
|
/**
|
|
147
|
-
* Reload the
|
|
148
|
-
*
|
|
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.
|
|
170
|
+
* Reload the legacy v1 store without notifying subscribers. Production v2
|
|
171
|
+
* reads Host snapshots instead; this remains only for isolated legacy tests.
|
|
153
172
|
*/
|
|
154
173
|
reloadFromStore(): void;
|
|
155
174
|
/**
|
|
@@ -186,5 +205,11 @@ export declare class BoardController {
|
|
|
186
205
|
/** Settle tasks left 'running' whose sessions already finished. */
|
|
187
206
|
private reconcileRunningTasks;
|
|
188
207
|
private persistAndNotify;
|
|
208
|
+
private commitRemote;
|
|
209
|
+
private performRemote;
|
|
210
|
+
private initializeRemote;
|
|
211
|
+
private doInitializeRemote;
|
|
212
|
+
private refreshRemote;
|
|
213
|
+
private acceptRemote;
|
|
189
214
|
private notify;
|
|
190
215
|
}
|
|
@@ -34,7 +34,9 @@ export declare function isValidCron(expr: string): boolean;
|
|
|
34
34
|
/**
|
|
35
35
|
* Compute the next matching instant after `fromMs` (ms epoch), in local time,
|
|
36
36
|
* at minute granularity, strictly greater than `fromMs`. Returns the ms epoch
|
|
37
|
-
* of the matching minute's start, or undefined when
|
|
38
|
-
*
|
|
37
|
+
* of the matching minute's start, or undefined when the calendar constraint
|
|
38
|
+
* can never match (for example `0 0 30 2 *`). The five-year horizon includes
|
|
39
|
+
* a full leap cycle, so a valid February 29 schedule remains reachable from
|
|
40
|
+
* every non-leap year.
|
|
39
41
|
*/
|
|
40
42
|
export declare function nextRunAtMs(expr: string, fromMs: number): number | undefined;
|
|
@@ -26,9 +26,8 @@ export interface ExecutionRecord {
|
|
|
26
26
|
error: string | undefined;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* A scheduled-run rule attached to a task. The
|
|
30
|
-
*
|
|
31
|
-
* persisted with the task (localStorage), so scheduling survives refreshes.
|
|
29
|
+
* A scheduled-run rule attached to a task. The Host scheduler triggers the
|
|
30
|
+
* task when `nextRunAt` is due and persists the rule in the Host ledger.
|
|
32
31
|
*/
|
|
33
32
|
export interface ScheduleRule {
|
|
34
33
|
/** Whether the schedule is armed. */
|
|
@@ -77,14 +76,15 @@ export interface TaskRecord {
|
|
|
77
76
|
permission?: TaskPermission;
|
|
78
77
|
/**
|
|
79
78
|
* When the task was archived (ms epoch). Archived tasks keep their status
|
|
80
|
-
* and execution history
|
|
79
|
+
* and execution history, leave the main board, and cannot run until restored;
|
|
80
|
+
* absent means on-board.
|
|
81
81
|
*/
|
|
82
82
|
archivedAt?: number;
|
|
83
83
|
}
|
|
84
84
|
/** Statuses a settled task may be archived from. */
|
|
85
85
|
export declare const ARCHIVABLE_STATUSES: readonly TaskStatus[];
|
|
86
86
|
/** Permission presets a task may pin on its execution session (the `/permission <id>` ids). */
|
|
87
|
-
export declare const TASK_PERMISSIONS: readonly [
|
|
87
|
+
export declare const TASK_PERMISSIONS: readonly ["read-only", "workspace-write", "danger-full-access"];
|
|
88
88
|
/** One permission preset id. */
|
|
89
89
|
export type TaskPermission = typeof TASK_PERMISSIONS[number];
|
|
90
90
|
/** Whether an unknown value is a known permission preset id. */
|
|
@@ -124,7 +124,7 @@ export declare const ALL_STATUSES: readonly TaskStatus[];
|
|
|
124
124
|
/** Brand an unknown string as a status; undefined when it is not one. */
|
|
125
125
|
export declare function isTaskStatus(value: unknown): value is TaskStatus;
|
|
126
126
|
/** Whether a manual move target is allowed from the given status. */
|
|
127
|
-
export declare function canMoveManually(
|
|
127
|
+
export declare function canMoveManually(from: TaskStatus, to: TaskStatus): boolean;
|
|
128
128
|
/** Normalize one optional execution-target string: trim; blank collapses to undefined. */
|
|
129
129
|
export declare function normalizeTargetId(value: string | undefined): string | undefined;
|
|
130
130
|
/** Create a task from user input. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Archive/restore task use case: move a settled (done/failed) task off the
|
|
3
|
-
* main board and back.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* main board and back. The task keeps its status, execution history, and
|
|
4
|
+
* transcript references, while archiving disarms any schedule so it cannot
|
|
5
|
+
* create more execution records until the user restores and re-enables it.
|
|
6
6
|
*/
|
|
7
7
|
import type { TaskRecord } from '../tasks.ts';
|
|
8
8
|
/** Result of an archive transition. */
|
|
@@ -15,7 +15,8 @@ export interface ArchiveTaskResult {
|
|
|
15
15
|
/**
|
|
16
16
|
* Archive one task: only settled statuses (done/failed) can be archived;
|
|
17
17
|
* a running or not-yet-settled task stays on the board (its runner still
|
|
18
|
-
* owns its lifecycle).
|
|
18
|
+
* owns its lifecycle). Archiving disarms a schedule; already-archived tasks
|
|
19
|
+
* are a no-op.
|
|
19
20
|
*/
|
|
20
21
|
export declare function applyArchiveTask(tasks: readonly TaskRecord[], id: string, now: number): ArchiveTaskResult;
|
|
21
22
|
/** Restore one task back onto the main board (clears the archive marker). */
|
|
@@ -12,9 +12,9 @@ export interface SetScheduleResult {
|
|
|
12
12
|
applied: boolean;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Set
|
|
16
|
-
* untouched); an enabled rule computes the
|
|
17
|
-
*
|
|
15
|
+
* Set an on-board task's schedule rule. A blank or invalid cron, or an
|
|
16
|
+
* archived task, is rejected (state untouched); an enabled rule computes the
|
|
17
|
+
* next run instant immediately, a disabled one carries no next-run instant.
|
|
18
18
|
* @param tasks - current ledger.
|
|
19
19
|
* @param id - the task to schedule.
|
|
20
20
|
* @param patch - rule fields to change (absent fields keep their current value).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH_HOME resolution shared by the plugin family's Host halves: the
|
|
3
|
+
* environment override wins, the platform home fallback follows. Mirrors
|
|
4
|
+
* what dsh-pet and dsh-liangshen each used to implement locally.
|
|
5
|
+
*/
|
|
6
|
+
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
7
|
+
export declare function expandHome(path: string, home?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the DSH home directory.
|
|
10
|
+
* @param env - process environment to read DSH_HOME from.
|
|
11
|
+
* @param home - platform home directory fallback (test seam).
|
|
12
|
+
* @returns the absolute DSH home path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDshHome(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
15
|
+
/** Resolve the DSH home directory from the live environment. */
|
|
16
|
+
export declare function dshHome(): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ExecutionRecord, type TaskRecord } from './core/tasks.ts';
|
|
2
|
+
import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts';
|
|
3
|
+
export interface LedgerState {
|
|
4
|
+
revision: number;
|
|
5
|
+
tasks: TaskRecord[];
|
|
6
|
+
scheduler: TaskBoardSchedulerSnapshot;
|
|
7
|
+
}
|
|
8
|
+
export interface OpenedRun {
|
|
9
|
+
task: TaskRecord;
|
|
10
|
+
execution: ExecutionRecord;
|
|
11
|
+
}
|
|
12
|
+
export declare class HostTaskLedger {
|
|
13
|
+
private readonly now;
|
|
14
|
+
private document;
|
|
15
|
+
private readonly listeners;
|
|
16
|
+
private readonly requestCache;
|
|
17
|
+
private readonly lockToken;
|
|
18
|
+
private lockFd;
|
|
19
|
+
readonly file: string;
|
|
20
|
+
readonly lockFile: string;
|
|
21
|
+
constructor(dir?: string, now?: () => number);
|
|
22
|
+
state(): LedgerState;
|
|
23
|
+
subscribe(listener: () => void): () => void;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
applyRequest(requestId: string, action: TaskBoardAction): {
|
|
26
|
+
state: LedgerState;
|
|
27
|
+
run?: OpenedRun;
|
|
28
|
+
};
|
|
29
|
+
openScheduled(taskId: string, nextRunAt: number | undefined, triggeredAt: number): OpenedRun | undefined;
|
|
30
|
+
skipMissed(now: number): void;
|
|
31
|
+
setScheduler(patch: Partial<TaskBoardSchedulerSnapshot>): void;
|
|
32
|
+
attachSession(taskId: string, executionId: string, sessionId: string): void;
|
|
33
|
+
settle(taskId: string, executionId: string, outcome: 'succeeded' | 'failed' | 'cancelled', error?: string): void;
|
|
34
|
+
private apply;
|
|
35
|
+
private repairSchedules;
|
|
36
|
+
private reconcileInterruptedStarts;
|
|
37
|
+
private load;
|
|
38
|
+
private syncRecentRequests;
|
|
39
|
+
private commit;
|
|
40
|
+
private notify;
|
|
41
|
+
private acquireLock;
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
3
|
+
import type { TaskBoardHostService } from './host-service.ts';
|
|
4
|
+
/** Header replaced by an authenticated same-host reverse proxy. */
|
|
5
|
+
export declare const TASK_BOARD_PROXY_TOKEN_HEADER = "x-dsh-task-board-proxy-token";
|
|
6
|
+
/** Optional authenticated reverse-proxy access layered over the loopback default. */
|
|
7
|
+
export interface TaskBoardRouteAccess {
|
|
8
|
+
trustedProxyHosts?: readonly string[];
|
|
9
|
+
proxyToken?: string;
|
|
10
|
+
}
|
|
11
|
+
interface ResolvedRouteAccess {
|
|
12
|
+
trustedProxyHosts: ReadonlySet<string>;
|
|
13
|
+
proxyToken?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Task-board route fence. Direct desktop access uses the repository-wide
|
|
17
|
+
* loopback socket + Host guard and additionally requires a browser same-origin
|
|
18
|
+
* marker, so a bare local curl cannot exercise the agent control plane.
|
|
19
|
+
* Authenticated proxies must be explicitly allowlisted and replace the
|
|
20
|
+
* internal token header after their own authentication step.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isTrustedTaskBoardRequest(req: IncomingMessage, access: ResolvedRouteAccess): boolean;
|
|
23
|
+
export declare function makeTaskBoardRoutes(service: TaskBoardHostService, access?: TaskBoardRouteAccess): WebRoute[];
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiProxy } from '@deepseek-ai/dsh-host-apiproxy';
|
|
2
|
+
import type { TaskRecord } from './core/tasks.ts';
|
|
3
|
+
export type ExecutionInspection = {
|
|
4
|
+
outcome: 'pending';
|
|
5
|
+
} | {
|
|
6
|
+
outcome: 'succeeded';
|
|
7
|
+
} | {
|
|
8
|
+
outcome: 'failed';
|
|
9
|
+
error: string;
|
|
10
|
+
} | {
|
|
11
|
+
outcome: 'cancelled';
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
/** A post-create launch failure that still identifies the session to the ledger. */
|
|
15
|
+
export declare class SessionLaunchError extends Error {
|
|
16
|
+
readonly sessionId: string;
|
|
17
|
+
constructor(sessionId: string, cause: unknown);
|
|
18
|
+
}
|
|
19
|
+
export declare class HostExecutionRunner {
|
|
20
|
+
private readonly api;
|
|
21
|
+
constructor(api: ApiProxy);
|
|
22
|
+
launch(task: TaskRecord): Promise<string>;
|
|
23
|
+
listRunning(): Promise<{
|
|
24
|
+
known: true;
|
|
25
|
+
count: number;
|
|
26
|
+
} | {
|
|
27
|
+
known: false;
|
|
28
|
+
}>;
|
|
29
|
+
inspect(sessionId: string, startedAt?: number): Promise<ExecutionInspection>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ApiProxy } from '@deepseek-ai/dsh-host-apiproxy';
|
|
2
|
+
import { HostTaskLedger } from './host-ledger.ts';
|
|
3
|
+
import { HostExecutionRunner } from './host-runner.ts';
|
|
4
|
+
import { PowerInhibitor } from './power-inhibitor.ts';
|
|
5
|
+
import type { TaskBoardAction, TaskBoardSnapshot } from './protocol.ts';
|
|
6
|
+
export declare class TaskBoardHostService {
|
|
7
|
+
readonly ledger: HostTaskLedger;
|
|
8
|
+
readonly runner: HostExecutionRunner;
|
|
9
|
+
readonly power: PowerInhibitor;
|
|
10
|
+
private readonly listeners;
|
|
11
|
+
private timers;
|
|
12
|
+
private lastScheduleTick;
|
|
13
|
+
private disposed;
|
|
14
|
+
private pollInFlight;
|
|
15
|
+
private tickInFlight;
|
|
16
|
+
private active;
|
|
17
|
+
private preventIdleSleep;
|
|
18
|
+
private readonly now;
|
|
19
|
+
constructor(api: ApiProxy, options?: {
|
|
20
|
+
ledger?: HostTaskLedger;
|
|
21
|
+
power?: PowerInhibitor;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
});
|
|
24
|
+
start(): void;
|
|
25
|
+
setConfiguration(active: boolean, preventIdleSleep: boolean): void;
|
|
26
|
+
snapshot(): TaskBoardSnapshot;
|
|
27
|
+
subscribe(listener: () => void): () => void;
|
|
28
|
+
apply(requestId: string, action: TaskBoardAction): TaskBoardSnapshot;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
private launch;
|
|
31
|
+
private pollSessions;
|
|
32
|
+
private reconcileExecutions;
|
|
33
|
+
private tickSchedule;
|
|
34
|
+
private armedSchedules;
|
|
35
|
+
private hasOpenExecutions;
|
|
36
|
+
private scheduleLaunch;
|
|
37
|
+
private schedulePoll;
|
|
38
|
+
private scheduleTick;
|
|
39
|
+
private syncPowerReasons;
|
|
40
|
+
private emit;
|
|
41
|
+
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host loader entry for the task-board plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* The section registers while this plugin is in the host composition (mount /
|
|
8
|
-
* DSH restart) and disappears when the plugin leaves it (unmount / restart),
|
|
9
|
-
* so agents always know the board exists and how to cooperate with it. The
|
|
10
|
-
* announcement can be turned off through the web settings plugin-configuration
|
|
11
|
-
* surface (`announceToAgent`); the section then disappears live.
|
|
4
|
+
* The Host owns the v2 ledger, action API, cron scheduler, session runner,
|
|
5
|
+
* execution reconciliation, and optional idle-sleep inhibitor. The browser is
|
|
6
|
+
* a same-origin asynchronous view over that service.
|
|
12
7
|
*/
|
|
13
8
|
import type { Context } from '@deepseek-ai/cordis';
|
|
14
9
|
import z from 'schemastery';
|
|
10
|
+
/** Default environment variable holding the authenticated proxy token. */
|
|
11
|
+
export declare const DEFAULT_PROXY_TOKEN_ENV = "DSH_TASK_BOARD_PROXY_TOKEN";
|
|
15
12
|
export declare const inject: string[];
|
|
16
13
|
/** 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\
|
|
14
|
+
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\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";
|
|
18
15
|
/**
|
|
19
16
|
* Settings namespace of the board's announcement capability — the section the
|
|
20
17
|
* web settings surface edits. Spelled here rather than imported: the browser
|
|
@@ -31,8 +28,19 @@ export interface Config {
|
|
|
31
28
|
announceToAgent?: boolean;
|
|
32
29
|
/** Master switch for the plugin (browser half + host announcement). */
|
|
33
30
|
enabled?: boolean;
|
|
31
|
+
/** Prevent idle system sleep while sessions run or schedules are armed. */
|
|
32
|
+
preventIdleSleep?: boolean;
|
|
33
|
+
/** Canonical reverse-proxy Host authorities admitted with a server-side token. */
|
|
34
|
+
trustedProxyHosts?: string[];
|
|
35
|
+
/** Environment variable whose value the authenticated proxy injects upstream. */
|
|
36
|
+
proxyTokenEnv?: string;
|
|
34
37
|
}
|
|
35
38
|
export declare const Config: z<Config>;
|
|
39
|
+
/** Resolve proxy access without ever placing the token value in plugin config. */
|
|
40
|
+
export declare function resolveProxyAccess(config: Config | undefined, env?: NodeJS.ProcessEnv): {
|
|
41
|
+
trustedProxyHosts: string[];
|
|
42
|
+
proxyToken?: string;
|
|
43
|
+
};
|
|
36
44
|
/**
|
|
37
45
|
* Register the board's announcement section, gated on the composition entry's
|
|
38
46
|
* `announceToAgent` (and the live settings value once the web settings
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback trust fence shared by the host route families: socket address,
|
|
3
|
+
* Host header, and browser same-origin markers. Packages receive this file as
|
|
4
|
+
* a generated copy via scripts/sync-shared.mjs; edit the shared source and
|
|
5
|
+
* re-run the sync instead of editing a copy.
|
|
6
|
+
*
|
|
7
|
+
* Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
|
|
8
|
+
* remote-web-ui gate), localhost hostnames, plus the browser same-origin
|
|
9
|
+
* markers (sec-fetch-site and Origin) for the request-level fence.
|
|
10
|
+
* @module dsh-web-ui-shared/host/loopback
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingMessage } from 'node:http';
|
|
13
|
+
/** IPv4 127/8 predicate (four decimal octets, first == 127). */
|
|
14
|
+
export declare function isIPv4Loopback(v4: string): boolean;
|
|
15
|
+
/** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
|
|
16
|
+
export declare function isLoopbackAddress(address: string | undefined): boolean;
|
|
17
|
+
/** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
|
|
18
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Request-level trust fence: a loopback socket address AND a loopback Host
|
|
21
|
+
* header, plus browser same-origin markers. The socket address is
|
|
22
|
+
* authoritative; X-Forwarded-For is never trusted.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isLoopbackRequest(request: IncomingMessage): boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ChildProcess, type SpawnSyncReturns } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import type { TaskBoardPowerSnapshot } from './protocol.ts';
|
|
4
|
+
declare const WINDOWS_HELPER: string;
|
|
5
|
+
declare const LINUX_HELPER: string;
|
|
6
|
+
declare const LINUX_INHIBIT_PATHS: readonly ["/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"];
|
|
7
|
+
export interface PowerReasons {
|
|
8
|
+
runningSessions: number;
|
|
9
|
+
armedSchedules: number;
|
|
10
|
+
sessionStateKnown: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface SpawnOptions {
|
|
13
|
+
shell: false;
|
|
14
|
+
windowsHide: boolean;
|
|
15
|
+
stdio: ['pipe', 'pipe', 'pipe'] | ['ignore', 'ignore', 'ignore'];
|
|
16
|
+
}
|
|
17
|
+
export type SpawnLike = (file: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
18
|
+
export type SpawnSyncLike = (file: string, args: readonly string[], options: {
|
|
19
|
+
stdio: 'ignore';
|
|
20
|
+
timeout: number;
|
|
21
|
+
windowsHide: boolean;
|
|
22
|
+
}) => Pick<SpawnSyncReturns<Buffer>, 'status' | 'error'>;
|
|
23
|
+
export interface PowerInhibitorOptions {
|
|
24
|
+
platform?: NodeJS.Platform;
|
|
25
|
+
pid?: number;
|
|
26
|
+
env?: NodeJS.ProcessEnv;
|
|
27
|
+
spawn?: SpawnLike;
|
|
28
|
+
spawnSync?: SpawnSyncLike;
|
|
29
|
+
exists?: typeof existsSync;
|
|
30
|
+
execPath?: string;
|
|
31
|
+
setTimeout?: typeof globalThis.setTimeout;
|
|
32
|
+
clearTimeout?: typeof globalThis.clearTimeout;
|
|
33
|
+
}
|
|
34
|
+
export declare class PowerInhibitor {
|
|
35
|
+
private readonly listeners;
|
|
36
|
+
private enabled;
|
|
37
|
+
private reasons;
|
|
38
|
+
private phase;
|
|
39
|
+
private child;
|
|
40
|
+
private retry;
|
|
41
|
+
private retryReset;
|
|
42
|
+
private retryIndex;
|
|
43
|
+
private lastError;
|
|
44
|
+
private stopping;
|
|
45
|
+
private readonly platform;
|
|
46
|
+
private readonly pid;
|
|
47
|
+
private readonly env;
|
|
48
|
+
private readonly spawn;
|
|
49
|
+
private readonly spawnSync;
|
|
50
|
+
private readonly exists;
|
|
51
|
+
private readonly execPath;
|
|
52
|
+
private readonly timer;
|
|
53
|
+
private readonly clearTimer;
|
|
54
|
+
private linuxProbe;
|
|
55
|
+
constructor(options?: PowerInhibitorOptions);
|
|
56
|
+
setEnabled(enabled: boolean): void;
|
|
57
|
+
updateReasons(reasons: PowerReasons): void;
|
|
58
|
+
subscribe(listener: () => void): () => void;
|
|
59
|
+
snapshot(): TaskBoardPowerSnapshot;
|
|
60
|
+
dispose(): void;
|
|
61
|
+
private desired;
|
|
62
|
+
private sync;
|
|
63
|
+
private acquire;
|
|
64
|
+
private markReady;
|
|
65
|
+
private fail;
|
|
66
|
+
private release;
|
|
67
|
+
private clearRetryReset;
|
|
68
|
+
private windowsPowerShell;
|
|
69
|
+
private linuxSystemdInhibit;
|
|
70
|
+
private spawnCommand;
|
|
71
|
+
private emit;
|
|
72
|
+
}
|
|
73
|
+
export { LINUX_HELPER, LINUX_INHIBIT_PATHS, WINDOWS_HELPER };
|