@linxin666/dsh-client-ui-task-board 0.3.6 → 0.3.10
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 +9 -4
- package/README.zh.md +9 -4
- package/lib/client.js +742 -75
- package/lib/client.js.map +1 -1
- package/lib/index.js +698 -190
- package/lib/types/client/TaskBoardSettingsCard.d.ts +2 -1
- package/lib/types/client/board/TaskBoard.d.ts +3 -0
- package/lib/types/client/board-mount.d.ts +4 -1
- package/lib/types/client/host-api.d.ts +2 -2
- package/lib/types/client/index.d.ts +10 -2
- package/lib/types/client/locales.d.ts +21 -0
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/sidebar-entry-core.d.ts +10 -0
- package/lib/types/client/sidebar-entry.d.ts +7 -1
- package/lib/types/core/controller.d.ts +11 -2
- package/lib/types/core/freeze-snapshot.d.ts +71 -0
- package/lib/types/core/handover.d.ts +58 -0
- package/lib/types/core/tasks.d.ts +70 -1
- package/lib/types/core/use-cases/task-update.d.ts +16 -7
- package/lib/types/host-ledger.d.ts +17 -2
- package/lib/types/host-runner.d.ts +43 -23
- package/lib/types/host-service.d.ts +8 -5
- package/lib/types/index.d.ts +9 -1
- package/lib/types/protocol.d.ts +15 -2
- package/package.json +26 -17
- package/src/client/TaskBoardSettingsCard.tsx +2 -1
- package/src/client/board/NewTaskModal.tsx +52 -0
- package/src/client/board/TaskBoard.tsx +5 -3
- package/src/client/board/TaskCard.tsx +3 -0
- package/src/client/board/TaskDetail.tsx +54 -0
- package/src/client/board-mount.tsx +11 -1
- package/src/client/host-api.ts +5 -5
- package/src/client/index.ts +76 -10
- package/src/client/locales.ts +55 -2
- package/src/client/settings-form.ts +78 -92
- package/src/client/sidebar-entry-core.ts +33 -7
- package/src/client/sidebar-entry.ts +7 -1
- package/src/core/controller.ts +35 -10
- package/src/core/freeze-snapshot.ts +213 -0
- package/src/core/handover.ts +117 -0
- package/src/core/store.ts +48 -1
- package/src/core/tasks.ts +87 -0
- package/src/core/use-cases/task-update.ts +30 -10
- package/src/dsh-home.ts +1 -1
- package/src/host-ledger.ts +137 -59
- package/src/host-routes.ts +1 -1
- package/src/host-runner.ts +211 -97
- package/src/host-service.ts +14 -10
- package/src/index.ts +24 -10
- package/src/protocol.ts +98 -14
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* the Web UI plugin group renders, bound to the `task-board` namespace.
|
|
5
5
|
*/
|
|
6
6
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
7
|
-
import type {
|
|
7
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
8
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
8
9
|
import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
|
|
9
10
|
/** The task-board fields this card edits (the namespace's full schema). */
|
|
10
11
|
export interface TaskBoardSettings {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type BoardController } from '../../core/controller.ts';
|
|
2
|
+
import { type TaskRecord } from '../../core/tasks.ts';
|
|
3
|
+
/** Case-insensitive title/description/freeze-snapshot match. */
|
|
4
|
+
export declare function matchesFilter(task: TaskRecord, filter: string): boolean;
|
|
2
5
|
/** Board component; subscribes to the controller snapshot. */
|
|
3
6
|
export declare function TaskBoard({ controller }: {
|
|
4
7
|
controller: BoardController;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { BoardController } from '../core/controller.ts';
|
|
2
|
+
import type { LocaleRefreshSource } from './sidebar-entry.ts';
|
|
2
3
|
/** The injected board container (kept in the DOM, hidden when inactive). */
|
|
3
4
|
export declare const BOARD_VIEW_SELECTOR = "[data-dsh-taskboard-view]";
|
|
4
5
|
/**
|
|
5
6
|
* Mount the board React tree into the center column and bind its visibility
|
|
6
7
|
* to the controller's boardOpen state.
|
|
7
8
|
* @param controller - the board controller driving the view.
|
|
9
|
+
* @param locale - locale-change source; when given, re-renders a mounted board
|
|
10
|
+
* on a Language switch.
|
|
8
11
|
* @returns disposer unmounting the tree and restoring the column.
|
|
9
12
|
*/
|
|
10
|
-
export declare function mountBoard(controller: BoardController): () => void;
|
|
13
|
+
export declare function mountBoard(controller: BoardController, locale?: LocaleRefreshSource): () => void;
|
|
@@ -3,7 +3,7 @@ import { type TaskBoardAction, type TaskBoardEventPayload, type TaskBoardSnapsho
|
|
|
3
3
|
export interface TaskBoardHostTransport {
|
|
4
4
|
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
5
5
|
state(): Promise<TaskBoardSnapshot>;
|
|
6
|
-
action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
|
|
6
|
+
action(action: TaskBoardAction, initiator?: string): Promise<TaskBoardSnapshot>;
|
|
7
7
|
subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void;
|
|
8
8
|
}
|
|
9
9
|
export declare class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
|
|
@@ -11,7 +11,7 @@ export declare class HttpTaskBoardHostTransport implements TaskBoardHostTranspor
|
|
|
11
11
|
constructor(storage?: Pick<Storage, 'getItem' | 'setItem'> | undefined);
|
|
12
12
|
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
13
13
|
state(): Promise<TaskBoardSnapshot>;
|
|
14
|
-
action(action: TaskBoardAction): Promise<TaskBoardSnapshot>;
|
|
14
|
+
action(action: TaskBoardAction, initiator?: string): Promise<TaskBoardSnapshot>;
|
|
15
15
|
private post;
|
|
16
16
|
private request;
|
|
17
17
|
subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* shell fails the whole boot when a plugin apply throws, and an external
|
|
9
9
|
* plugin must not take the GUI down.
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
12
13
|
import { type TaskBoardKey } from './locales.ts';
|
|
13
14
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
14
15
|
interface LocaleNamespaceMap {
|
|
@@ -46,7 +47,14 @@ declare module '@deepseek-ai/cordis' {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Required services (fiber inject waiting — the runtime must be up first).
|
|
52
|
+
* The generated remote faces are probed at use time instead of injected:
|
|
53
|
+
* `remote.agentPresets` only registers on 0.1.2-alpha.2 hosts (the
|
|
54
|
+
* api-remotes contribution), so a hard wait would pend the entry forever
|
|
55
|
+
* on hosts below that cohort, which serve the same roster through the
|
|
56
|
+
* connection RPC face.
|
|
57
|
+
*/
|
|
50
58
|
export declare const inject: string[];
|
|
51
59
|
/**
|
|
52
60
|
* Mount the task board.
|
|
@@ -39,6 +39,24 @@ export declare const zh: {
|
|
|
39
39
|
'new.submit': string;
|
|
40
40
|
'new.cancel': string;
|
|
41
41
|
'new.required': string;
|
|
42
|
+
'new.freeze': string;
|
|
43
|
+
'new.freezePlaceholder': string;
|
|
44
|
+
'detail.freeze': string;
|
|
45
|
+
'detail.freeze.goal': string;
|
|
46
|
+
'detail.freeze.progress': string;
|
|
47
|
+
'detail.freeze.next': string;
|
|
48
|
+
'detail.freeze.frozenAt': string;
|
|
49
|
+
'detail.freeze.frozenBy': string;
|
|
50
|
+
'detail.freeze.redacted': string;
|
|
51
|
+
'card.frozen': string;
|
|
52
|
+
'new.handover': string;
|
|
53
|
+
'new.handoverPlaceholder': string;
|
|
54
|
+
'detail.handover': string;
|
|
55
|
+
'detail.handover.bundledAt': string;
|
|
56
|
+
'detail.handover.references': string;
|
|
57
|
+
'detail.permissionPending': string;
|
|
58
|
+
'detail.permissionConfirm': string;
|
|
59
|
+
'detail.permissionConfirmed': string;
|
|
42
60
|
'detail.title': string;
|
|
43
61
|
'detail.edit': string;
|
|
44
62
|
'detail.close': string;
|
|
@@ -57,6 +75,7 @@ export declare const zh: {
|
|
|
57
75
|
'detail.viewSession': string;
|
|
58
76
|
'detail.noSession': string;
|
|
59
77
|
'detail.executionStarted': string;
|
|
78
|
+
'detail.execution.initiator': string;
|
|
60
79
|
'detail.executionEnded': string;
|
|
61
80
|
'detail.result.succeeded': string;
|
|
62
81
|
'detail.result.failed': string;
|
|
@@ -136,5 +155,7 @@ export type TaskBoardKey = keyof typeof zh;
|
|
|
136
155
|
export type SettingsCardKey = TaskBoardKey;
|
|
137
156
|
/** Active dictionary, picked by the document language at call time. */
|
|
138
157
|
export declare function dictionary(): Record<TaskBoardKey, string>;
|
|
158
|
+
/** Wire the SDK translate seat; pass undefined to restore the document-language pick. */
|
|
159
|
+
export declare function setRuntimeTranslate(t: ((key: TaskBoardKey, params?: Record<string, string>) => string) | undefined): void;
|
|
139
160
|
/** Translate a key with optional {name} template params. */
|
|
140
161
|
export declare function t(key: TaskBoardKey, params?: Record<string, string>): string;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* each plugin's client bundle; mirrors the official ui-plugin-config
|
|
7
7
|
* card-store pattern.
|
|
8
8
|
*/
|
|
9
|
-
import type { SettingsScope
|
|
9
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
10
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
10
11
|
/** The write one field's staged text performs when the card is saved. */
|
|
11
12
|
export type FieldWrite = {
|
|
12
13
|
kind: 'set';
|
|
@@ -21,9 +22,11 @@ export interface FieldSpec {
|
|
|
21
22
|
/**
|
|
22
23
|
* Whether the Host treats this field as a secret and redacts its value from
|
|
23
24
|
* the read-back (role('secret') in the section schema). Redacted secrets are
|
|
24
|
-
* never compared against the draft on save
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* never compared against the draft on save: the Host strips them from every
|
|
26
|
+
* wire view layer, so the settled snapshot carries nothing to read back. A
|
|
27
|
+
* staged secret set is judged by the mutation settling; the rest of its
|
|
28
|
+
* batch, when one exists, is still judged by read-back, and the atomic
|
|
29
|
+
* mutation lands every write or none.
|
|
27
30
|
*/
|
|
28
31
|
secret?: boolean;
|
|
29
32
|
/** Render a stored value as draft text; the empty string when the section carries none. */
|
|
@@ -49,8 +52,8 @@ export interface CardShell {
|
|
|
49
52
|
available: boolean;
|
|
50
53
|
/**
|
|
51
54
|
* Whether the namespace is actually served to this client. False when the
|
|
52
|
-
* Host deployment does not expose it (e.g. the
|
|
53
|
-
*
|
|
55
|
+
* Host deployment does not expose it (e.g. the owning plugin's settings
|
|
56
|
+
* domain is not mounted): the card renders an explanation
|
|
54
57
|
* instead of its form, so a missing namespace never looks like a missing
|
|
55
58
|
* plugin.
|
|
56
59
|
*/
|
|
@@ -83,7 +86,7 @@ export interface CardActions {
|
|
|
83
86
|
/** Drop every staged edit. */
|
|
84
87
|
discard: () => void;
|
|
85
88
|
}
|
|
86
|
-
/** One durable write
|
|
89
|
+
/** One durable write inside the save's atomic scope mutation. */
|
|
87
90
|
export interface BatchedWrite {
|
|
88
91
|
/** Field this entry writes. */
|
|
89
92
|
field: string;
|
|
@@ -92,28 +95,6 @@ export interface BatchedWrite {
|
|
|
92
95
|
/** Value for op set (absent for unset). */
|
|
93
96
|
value?: unknown;
|
|
94
97
|
}
|
|
95
|
-
/** Per-field outcome of one batched scope write. */
|
|
96
|
-
export interface BatchedFieldResult {
|
|
97
|
-
/** Field this entry writes. */
|
|
98
|
-
field: string;
|
|
99
|
-
/** Whether the Host accepted this field's write (per the read-back view). */
|
|
100
|
-
landed: boolean;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Result of a batched scope write. The bridge scope posts every planned write
|
|
104
|
-
* in one /mutate so the Host validate hook judges baseURL+model together; a
|
|
105
|
-
* batched refusal fails the whole save rather than per-field.
|
|
106
|
-
*/
|
|
107
|
-
export interface BatchResult {
|
|
108
|
-
/** Whether the whole mutate was accepted. */
|
|
109
|
-
ok: boolean;
|
|
110
|
-
/** Per-field success, in the request order (always present when ok). */
|
|
111
|
-
fields: BatchedFieldResult[];
|
|
112
|
-
/** Host rejection code (mutate refused). */
|
|
113
|
-
code?: string;
|
|
114
|
-
/** Host rejection message (mutate refused). */
|
|
115
|
-
message?: string;
|
|
116
|
-
}
|
|
117
98
|
/** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */
|
|
118
99
|
export interface NumberConstraints {
|
|
119
100
|
/** The accepted value must be a whole number. */
|
|
@@ -128,8 +109,8 @@ export declare function textField(field: string): FieldSpec;
|
|
|
128
109
|
/**
|
|
129
110
|
* A free-text field the Host treats as a secret and redacts from the read-back
|
|
130
111
|
* (role('secret') in the section schema). The card still edits it like text,
|
|
131
|
-
* but a save never compares the redacted value back
|
|
132
|
-
*
|
|
112
|
+
* but a save never compares the redacted value back: the staged set is judged
|
|
113
|
+
* by the mutation settling (see {@link FieldSpec.secret}).
|
|
133
114
|
*/
|
|
134
115
|
export declare function secretField(field: string): FieldSpec;
|
|
135
116
|
/** A boolean field, edited through true/false draft text. */
|
|
@@ -171,19 +152,22 @@ export declare class CardForm<T> {
|
|
|
171
152
|
/** The actions the card's slot registration injects. */
|
|
172
153
|
actions(): CardActions;
|
|
173
154
|
/**
|
|
174
|
-
* Write every staged edit, then re-seed from
|
|
155
|
+
* Write every staged edit in one atomic scope mutation, then re-seed from
|
|
156
|
+
* what the Host accepted.
|
|
175
157
|
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
158
|
+
* The whole batch rides one mutate, so cross-field validate hooks
|
|
159
|
+
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
160
|
+
* or refuses the batch. The 0.1.2 scope contract never rejects a refused
|
|
161
|
+
* mutation — the scope recovers with a fresh Host view and resolves — so
|
|
162
|
+
* resolution alone proves nothing: the outcome is judged by reading the
|
|
163
|
+
* settled snapshot back, one planned write at a time, and one missed write
|
|
164
|
+
* fails the whole save. A scope that still rejects on refusal (the dsh-web
|
|
165
|
+
* bridge scope) reports through the same failure path with its rejection
|
|
166
|
+
* message. A save that did not land keeps its drafts, so the user can
|
|
167
|
+
* correct them instead of retyping.
|
|
168
|
+
* @returns settlement after the mutation and the read-back.
|
|
183
169
|
*/
|
|
184
170
|
save(): Promise<void>;
|
|
185
|
-
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
186
|
-
private batchedScope;
|
|
187
171
|
/**
|
|
188
172
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
189
173
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -192,8 +176,16 @@ export declare class CardForm<T> {
|
|
|
192
176
|
* @returns the planned writes, in the order the fields were staged.
|
|
193
177
|
*/
|
|
194
178
|
private plan;
|
|
195
|
-
|
|
196
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Read-back judgment for a planned set: the user layer must hold the
|
|
181
|
+
* intended value once the mutation has settled.
|
|
182
|
+
*/
|
|
183
|
+
private landedSet;
|
|
184
|
+
/**
|
|
185
|
+
* Read-back judgment for a planned unset: the field must be gone from the
|
|
186
|
+
* user layer once the mutation has settled.
|
|
187
|
+
*/
|
|
188
|
+
private landedUnset;
|
|
197
189
|
private stage;
|
|
198
190
|
private specOf;
|
|
199
191
|
private snapshotOf;
|
|
@@ -34,6 +34,16 @@ export interface SidebarEntryOptions {
|
|
|
34
34
|
label(): string;
|
|
35
35
|
/** Optional localized tooltip (title attribute). */
|
|
36
36
|
tooltip?(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Optional locale-change subscription: re-applies label / aria-label /
|
|
39
|
+
* tooltip whenever the active locale changes. Plain-DOM rows would
|
|
40
|
+
* otherwise keep the label captured at mount; pass the SDK locale runtime
|
|
41
|
+
* subscription (ctx.locale.subscribe) so the row follows the language
|
|
42
|
+
* switch without a reload.
|
|
43
|
+
*/
|
|
44
|
+
refresh?: {
|
|
45
|
+
subscribe(listener: () => void): () => void;
|
|
46
|
+
};
|
|
37
47
|
/** Click action (open/toggle the owning panel). */
|
|
38
48
|
onToggle(): void;
|
|
39
49
|
/** Family-block position: 'before' inserts ahead of sibling plugin rows, 'after' behind them. */
|
|
@@ -14,10 +14,16 @@
|
|
|
14
14
|
import type { BoardController } from '../core/controller.ts';
|
|
15
15
|
/** Stable data attribute identifying the injected entry row. */
|
|
16
16
|
export declare const ENTRY_SELECTOR = "[data-dsh-taskboard-entry]";
|
|
17
|
+
/** Locale-change subscription the shared core asks for (ctx.locale.subscribe shape). */
|
|
18
|
+
export interface LocaleRefreshSource {
|
|
19
|
+
subscribe(listener: () => void): () => void;
|
|
20
|
+
}
|
|
17
21
|
/**
|
|
18
22
|
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
19
23
|
* on later React re-renders.
|
|
20
24
|
* @param controller - the board controller the entry toggles.
|
|
25
|
+
* @param locale - locale-change source; when given, re-applies the label on
|
|
26
|
+
* a Language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
21
27
|
* @returns disposer removing the entry and its observers.
|
|
22
28
|
*/
|
|
23
|
-
export declare function mountSidebarEntry(controller: BoardController): () => void;
|
|
29
|
+
export declare function mountSidebarEntry(controller: BoardController, locale?: LocaleRefreshSource): () => void;
|
|
@@ -18,7 +18,11 @@ import type { TaskBoardAction, TaskBoardEventPayload, TaskBoardSnapshot } from '
|
|
|
18
18
|
export interface TaskBoardTransport {
|
|
19
19
|
bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
|
|
20
20
|
state(): Promise<TaskBoardSnapshot>;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Submit one action; the optional initiator is the current DSH session id
|
|
23
|
+
* (issue #6 audit origin), asserted by the client and recorded by the Host.
|
|
24
|
+
*/
|
|
25
|
+
action(action: TaskBoardAction, initiator?: string): Promise<TaskBoardSnapshot>;
|
|
22
26
|
subscribe(listener: (event?: TaskBoardEventPayload) => void): () => void;
|
|
23
27
|
}
|
|
24
28
|
/** The sessions face the controller needs for navigation awareness. */
|
|
@@ -74,7 +78,7 @@ export interface ControllerSnapshot {
|
|
|
74
78
|
executionOptions: ExecutionOptionsSnapshot;
|
|
75
79
|
pendingTaskIds: readonly string[];
|
|
76
80
|
transportError?: string;
|
|
77
|
-
host?: Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power'>;
|
|
81
|
+
host?: Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission'>;
|
|
78
82
|
}
|
|
79
83
|
/** The selected task (resolved from the ledger), or undefined. */
|
|
80
84
|
export declare function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined;
|
|
@@ -186,6 +190,11 @@ export declare class BoardController {
|
|
|
186
190
|
* the run is refused (returns false).
|
|
187
191
|
*/
|
|
188
192
|
runTask(id: string): Promise<boolean>;
|
|
193
|
+
/**
|
|
194
|
+
* Confirm a card's above-default permission binding through the Host
|
|
195
|
+
* (resolves the pending-confirmation transaction; no-op otherwise).
|
|
196
|
+
*/
|
|
197
|
+
confirmPermission(id: string): Promise<boolean>;
|
|
189
198
|
/** Re-run a settled task through the Host (the Host replans and executes). */
|
|
190
199
|
rerunTask(id: string): Promise<void>;
|
|
191
200
|
/**
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freeze-request parser and security gate: parses the system-prompt-agreed
|
|
3
|
+
* freeze format into a structured context snapshot (goal / progress / next),
|
|
4
|
+
* redacts sensitive patterns to markers, rejects slash-prefixed DSH command
|
|
5
|
+
* lines, and enforces the 8 KiB per-field byte limit.
|
|
6
|
+
* Pure functions only: no session, network, or filesystem access.
|
|
7
|
+
*/
|
|
8
|
+
/** Hard per-field limit: 8 KiB measured in UTF-8 bytes. */
|
|
9
|
+
export declare const FREEZE_FIELD_BYTE_LIMIT: number;
|
|
10
|
+
/** Marker replacing every sensitive match. */
|
|
11
|
+
export declare const REDACTED_MARKER = "[REDACTED]";
|
|
12
|
+
/** Structured context snapshot carried by a continuation card. */
|
|
13
|
+
export interface FreezeSnapshot {
|
|
14
|
+
goal: string;
|
|
15
|
+
progress: string;
|
|
16
|
+
next: string;
|
|
17
|
+
}
|
|
18
|
+
/** Non-fatal notices produced by the security gate. */
|
|
19
|
+
export type FreezeWarning = 'redacted';
|
|
20
|
+
export type FreezeResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
snapshot: FreezeSnapshot;
|
|
23
|
+
warnings: FreezeWarning[];
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
error: {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/** Redact sensitive patterns to the marker; reports whether any hit occurred. */
|
|
32
|
+
export declare function redactSensitive(text: string): {
|
|
33
|
+
text: string;
|
|
34
|
+
redacted: boolean;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* True when any line of the text starts with "/" (a DSH command line).
|
|
38
|
+
* Leading horizontal whitespace before the slash counts too: a frozen
|
|
39
|
+
* body line like ` /kill` is still a command, not prose.
|
|
40
|
+
*/
|
|
41
|
+
export declare function hasSlashCommandLines(text: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Sanitize a structured freeze snapshot (the create/update action payload):
|
|
44
|
+
* shape check, slash-command taint rejection, sensitive redaction, and the
|
|
45
|
+
* per-field byte cap - the same gate parseFreezeRequest applies to
|
|
46
|
+
* free-text freeze requests, exposed for the action data plane (issue #4).
|
|
47
|
+
* @param value - the freeze object carried by an action or read back from disk.
|
|
48
|
+
* @param extraKeys - keys allowed alongside goal/progress/next (e.g. the
|
|
49
|
+
* protocol redacted flag, the ledger frozenAt stamp) and preserved verbatim
|
|
50
|
+
* when present; their validation stays with the caller.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sanitizeFreezeSnapshot(value: unknown, extraKeys?: readonly string[]): {
|
|
53
|
+
ok: true;
|
|
54
|
+
snapshot: FreezeSnapshot;
|
|
55
|
+
redacted: boolean;
|
|
56
|
+
extras: Record<string, unknown>;
|
|
57
|
+
} | {
|
|
58
|
+
ok: false;
|
|
59
|
+
error: {
|
|
60
|
+
code: string;
|
|
61
|
+
message: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Parse the freeze-request format: a <<<FREEZE ... >>>FREEZE block whose body
|
|
66
|
+
* is 目标: / 进度: / 下一步: section headers, each followed by body lines.
|
|
67
|
+
* The gate applies before returning: slash-command taint rejects the whole
|
|
68
|
+
* request, sensitive patterns are redacted to markers, and each field is
|
|
69
|
+
* capped at FREEZE_FIELD_BYTE_LIMIT UTF-8 bytes.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseFreezeRequest(input: string): FreezeResult;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handover bundle (issue #5, ADR 0001): the resource reference set a
|
|
3
|
+
* continuation card carries — the pinned execution triplet (workspace /
|
|
4
|
+
* agent preset / permission) plus doc/script references — so a picking-up
|
|
5
|
+
* session can start with zero context.
|
|
6
|
+
*
|
|
7
|
+
* Security gate (adversarial scenario b): a bundle whose effective
|
|
8
|
+
* permission is HIGHER than the session default is an unconfirmed binding.
|
|
9
|
+
* It enters the pending-confirmation state (the card stores the binding
|
|
10
|
+
* without a permissionConfirmedAt stamp); manual execution refuses, cron
|
|
11
|
+
* refuses to schedule, and the human confirm-permission action resolves the
|
|
12
|
+
* transaction by stamping the confirmation. Any later permission or
|
|
13
|
+
* handover change re-arms the gate (the confirmation binds the exact
|
|
14
|
+
* permission value, blocking confirm-then-swap escalation).
|
|
15
|
+
*/
|
|
16
|
+
import { type TaskPermission, type TaskRecord } from './tasks.ts';
|
|
17
|
+
export type { TaskPermission };
|
|
18
|
+
/** Wire shape of a handover bundle (before bundledAt is stamped). */
|
|
19
|
+
export interface TaskHandoverInput {
|
|
20
|
+
workspaceId?: string;
|
|
21
|
+
mode?: string;
|
|
22
|
+
permission?: TaskPermission;
|
|
23
|
+
references: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Persisted handover bundle on a task record. */
|
|
26
|
+
export interface TaskHandover extends TaskHandoverInput {
|
|
27
|
+
/** When the bundle was attached/replaced (ms epoch, stamped by the use case). */
|
|
28
|
+
bundledAt: number;
|
|
29
|
+
}
|
|
30
|
+
/** Most references a bundle may carry. */
|
|
31
|
+
export declare const HANDOVER_MAX_REFERENCES = 32;
|
|
32
|
+
/** Per-reference UTF-8 byte cap. */
|
|
33
|
+
export declare const HANDOVER_MAX_REFERENCE_BYTES = 512;
|
|
34
|
+
/** Total references UTF-8 byte cap. */
|
|
35
|
+
export declare const HANDOVER_MAX_TOTAL_BYTES: number;
|
|
36
|
+
/** Per target-id (workspace/preset) byte cap. */
|
|
37
|
+
export declare const HANDOVER_MAX_TARGET_BYTES = 256;
|
|
38
|
+
/** The board's notion of the deployment session-default permission (fail-safe default). */
|
|
39
|
+
export declare const DEFAULT_SESSION_PERMISSION: TaskPermission;
|
|
40
|
+
/** Permission elevation rank (higher = more authority). */
|
|
41
|
+
export declare const PERMISSION_RANK: ReadonlyMap<TaskPermission, number>;
|
|
42
|
+
/**
|
|
43
|
+
* Gate a handover bundle from the wire or disk: exact keys, string targets
|
|
44
|
+
* under the byte cap, a known permission, and a bounded string reference
|
|
45
|
+
* list. Returns the sanitized bundle, or undefined when rejected.
|
|
46
|
+
*/
|
|
47
|
+
export declare function sanitizeHandover(value: unknown): TaskHandoverInput | undefined;
|
|
48
|
+
/** Build the persisted bundle from a sanitized input, stamping bundledAt. */
|
|
49
|
+
export declare function handoverOf(input: TaskHandoverInput, now: number): TaskHandover;
|
|
50
|
+
/** The permission an execution session would actually run under. */
|
|
51
|
+
export declare function effectivePermission(task: Pick<TaskRecord, 'permission' | 'handover'>): TaskPermission | undefined;
|
|
52
|
+
/** Whether a binding's permission is elevated above the session default. */
|
|
53
|
+
export declare function exceedsSessionDefault(permission: TaskPermission | undefined, sessionDefault: TaskPermission): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The confirmation-gate predicate: an elevated permission without a human
|
|
56
|
+
* confirmation stamp. Manual run/rerun and cron must refuse such a card.
|
|
57
|
+
*/
|
|
58
|
+
export declare function requiresPermissionConfirmation(task: Pick<TaskRecord, 'permission' | 'handover' | 'permissionConfirmedAt'>, sessionDefault?: TaskPermission): boolean;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Framework-free (no cordis, no runtime imports) so the state machine is
|
|
5
5
|
* unit-testable in isolation.
|
|
6
6
|
*/
|
|
7
|
+
import type { FreezeSnapshot } from './freeze-snapshot.ts';
|
|
8
|
+
import type { TaskHandover, TaskHandoverInput } from './handover.ts';
|
|
7
9
|
/** Task lifecycle status, one per kanban column. */
|
|
8
10
|
export type TaskStatus = 'backlog' | 'todo' | 'running' | 'done' | 'failed';
|
|
9
11
|
/**
|
|
@@ -24,6 +26,16 @@ export interface ExecutionRecord {
|
|
|
24
26
|
result: 'succeeded' | 'failed' | 'cancelled' | undefined;
|
|
25
27
|
/** Human failure text when the run failed (prompt rejection or agent error). */
|
|
26
28
|
error: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Session id of the DSH session that issued the run/rerun action (issue #6
|
|
31
|
+
* audit origin). Client-asserted, not a trust boundary; absent when the run
|
|
32
|
+
* was triggered by cron (source unknown).
|
|
33
|
+
*/
|
|
34
|
+
initiatedBy?: string;
|
|
35
|
+
/** Freeze instant captured from the card snapshot when the run opened. */
|
|
36
|
+
frozenAt?: number;
|
|
37
|
+
/** Freeze source session captured from the card snapshot when the run opened. */
|
|
38
|
+
frozenBy?: string;
|
|
27
39
|
}
|
|
28
40
|
/**
|
|
29
41
|
* Maximum number of execution records retained per task. Older settled runs
|
|
@@ -52,6 +64,23 @@ export interface ScheduleRule {
|
|
|
52
64
|
/** Instant of the latest scheduled trigger (ms epoch). */
|
|
53
65
|
lastTriggeredAt: number | undefined;
|
|
54
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Frozen context snapshot carried by a continuation card (issue #4): the
|
|
69
|
+
* goal/progress/next text (already sanitized by the freeze gates) plus the
|
|
70
|
+
* freeze instant and the redaction warning flag.
|
|
71
|
+
*/
|
|
72
|
+
export interface TaskFreeze extends FreezeSnapshot {
|
|
73
|
+
/** When the snapshot was frozen (ms epoch, stamped by the create/update use case). */
|
|
74
|
+
frozenAt: number;
|
|
75
|
+
/** True when the freeze gates redacted sensitive patterns out of the text. */
|
|
76
|
+
redacted?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Session id of the DSH session that authored the frozen snapshot (issue #6
|
|
79
|
+
* provenance): stamped by the Host ledger from the create/update action's
|
|
80
|
+
* initiator, or kept from the wire payload when no initiator was asserted.
|
|
81
|
+
*/
|
|
82
|
+
frozenBy?: string;
|
|
83
|
+
}
|
|
55
84
|
/** One task on the board. */
|
|
56
85
|
export interface TaskRecord {
|
|
57
86
|
/** Stable task id (uuid). */
|
|
@@ -90,6 +119,25 @@ export interface TaskRecord {
|
|
|
90
119
|
* `/permission <id>` slash command; absent leaves the session default.
|
|
91
120
|
*/
|
|
92
121
|
permission?: TaskPermission;
|
|
122
|
+
/**
|
|
123
|
+
* Frozen context snapshot for a continuation card; absent on plain tasks.
|
|
124
|
+
* Sanitized before it enters the ledger (redaction, slash-command taint,
|
|
125
|
+
* 8 KiB per-field cap) by the protocol gate and re-normalized on load.
|
|
126
|
+
*/
|
|
127
|
+
freeze?: TaskFreeze;
|
|
128
|
+
/**
|
|
129
|
+
* Handover bundle carried by a continuation card (issue #5): the pinned
|
|
130
|
+
* execution triplet plus doc/script references. Sanitized before it
|
|
131
|
+
* enters the ledger by the protocol gate and re-normalized on load; the
|
|
132
|
+
* bundle's triplet overrides the legacy pin fields at execution time.
|
|
133
|
+
*/
|
|
134
|
+
handover?: TaskHandover;
|
|
135
|
+
/**
|
|
136
|
+
* Human confirmation stamp for an above-default effective permission
|
|
137
|
+
* (ms epoch). Absent while the binding awaits confirmation; any permission
|
|
138
|
+
* or handover change re-arms the gate by clearing it.
|
|
139
|
+
*/
|
|
140
|
+
permissionConfirmedAt?: number;
|
|
93
141
|
/**
|
|
94
142
|
* When the task was archived (ms epoch). Archived tasks keep their status
|
|
95
143
|
* and execution history, leave the main board, and cannot run until restored;
|
|
@@ -125,6 +173,19 @@ export interface NewTaskInput {
|
|
|
125
173
|
enabled: boolean;
|
|
126
174
|
cron: string;
|
|
127
175
|
};
|
|
176
|
+
/**
|
|
177
|
+
* Optional frozen context snapshot (goal/progress/next, sanitized by the
|
|
178
|
+
* protocol gate) turning the new task into a continuation card.
|
|
179
|
+
*/
|
|
180
|
+
freeze?: FreezeSnapshot & {
|
|
181
|
+
redacted?: boolean;
|
|
182
|
+
frozenBy?: string;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Optional handover bundle (pinned triplet + doc/script references,
|
|
186
|
+
* sanitized by the protocol gate) attached at creation.
|
|
187
|
+
*/
|
|
188
|
+
handover?: TaskHandoverInput;
|
|
128
189
|
}
|
|
129
190
|
/** The five kanban columns, in display order. */
|
|
130
191
|
export declare const COLUMNS: readonly {
|
|
@@ -143,6 +204,14 @@ export declare function isTaskStatus(value: unknown): value is TaskStatus;
|
|
|
143
204
|
export declare function canMoveManually(from: TaskStatus, to: TaskStatus): boolean;
|
|
144
205
|
/** Normalize one optional execution-target string: trim; blank collapses to undefined. */
|
|
145
206
|
export declare function normalizeTargetId(value: string | undefined): string | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* Build the persisted freeze snapshot from a sanitized input, stamping the
|
|
209
|
+
* freeze instant (shared by the create and update use cases).
|
|
210
|
+
*/
|
|
211
|
+
export declare function freezeOf(input: FreezeSnapshot & {
|
|
212
|
+
redacted?: boolean;
|
|
213
|
+
frozenBy?: string;
|
|
214
|
+
}, now: number): TaskFreeze;
|
|
146
215
|
/** Create a task from user input. */
|
|
147
216
|
export declare function createTask(input: NewTaskInput, now: number, id: string): TaskRecord;
|
|
148
217
|
/** Clone a task with an updated status and a fresh updatedAt. */
|
|
@@ -158,7 +227,7 @@ export declare function withSchedule(task: TaskRecord, patch: Partial<ScheduleRu
|
|
|
158
227
|
* Open a fresh execution on a task: move it to 'running' and append a
|
|
159
228
|
* running execution record. Returns the new task and the new execution.
|
|
160
229
|
*/
|
|
161
|
-
export declare function startExecution(task: TaskRecord, now: number, executionId: string): {
|
|
230
|
+
export declare function startExecution(task: TaskRecord, now: number, executionId: string, initiatedBy?: string): {
|
|
162
231
|
task: TaskRecord;
|
|
163
232
|
execution: ExecutionRecord;
|
|
164
233
|
};
|
|
@@ -4,16 +4,25 @@
|
|
|
4
4
|
* fresh updatedAt. Pure ledger transition (no persistence or notify — the
|
|
5
5
|
* controller orchestrates those).
|
|
6
6
|
*
|
|
7
|
-
* An explicit `undefined` in the patch clears
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* can never persist a value the execution service rejects.
|
|
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.
|
|
11
10
|
*/
|
|
12
11
|
import { type TaskRecord } from '../tasks.ts';
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
import type { FreezeSnapshot } from '../freeze-snapshot.ts';
|
|
13
|
+
import type { TaskHandoverInput } from '../handover.ts';
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* Editable fields on a task (the update patch surface). `freeze` replaces the
|
|
16
|
+
* continuation-card snapshot (restamping frozenAt); an explicit null clears it.
|
|
17
|
+
*/
|
|
18
|
+
export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission'>> & {
|
|
19
|
+
freeze?: FreezeSnapshot & {
|
|
20
|
+
redacted?: boolean;
|
|
21
|
+
} | null;
|
|
22
|
+
/** Replaces the handover bundle (restamping bundledAt); an explicit null clears it. */
|
|
23
|
+
handover?: TaskHandoverInput | null;
|
|
24
|
+
};
|
|
25
|
+
/** The fields that edit the task's content (what the user reads and what the
|
|
17
26
|
* next execution sends). Unlike the execution targets they stay editable only
|
|
18
27
|
* while the task has never started executing — after the first run the
|
|
19
28
|
* recorded prompt is the record of what actually ran, so it becomes read-only.
|