@linxin666/dsh-client-ui-task-board 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +42 -11
  3. package/README.zh.md +42 -11
  4. package/lib/client.js +4958 -472
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2841 -121
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -3
  9. package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
  10. package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
  11. package/lib/types/client/board/EditTaskModal.d.ts +5 -2
  12. package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
  13. package/lib/types/client/board/NewTaskModal.d.ts +4 -1
  14. package/lib/types/client/board/TagManagerModal.d.ts +26 -0
  15. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  16. package/lib/types/client/board/TaskCard.d.ts +14 -1
  17. package/lib/types/client/board/TaskDetail.d.ts +3 -1
  18. package/lib/types/client/board/TaskForm.d.ts +4 -1
  19. package/lib/types/client/board/VerificationReport.d.ts +23 -0
  20. package/lib/types/client/board/card-view.d.ts +113 -0
  21. package/lib/types/client/board/icons.d.ts +32 -0
  22. package/lib/types/client/board/overlay.d.ts +69 -0
  23. package/lib/types/client/board/status-key.d.ts +15 -0
  24. package/lib/types/client/board/task-markdown.d.ts +19 -0
  25. package/lib/types/client/board-card-seat.d.ts +34 -0
  26. package/lib/types/client/index.d.ts +19 -0
  27. package/lib/types/client/locales.d.ts +103 -1
  28. package/lib/types/client/native-panel.d.ts +6 -20
  29. package/lib/types/client/seats.d.ts +18 -0
  30. package/lib/types/client/service.d.ts +32 -0
  31. package/lib/types/client/settings-form.d.ts +16 -0
  32. package/lib/types/core/controller.d.ts +37 -1
  33. package/lib/types/core/extension.d.ts +283 -0
  34. package/lib/types/core/handover.d.ts +13 -1
  35. package/lib/types/core/issue-form.d.ts +16 -0
  36. package/lib/types/core/tasks.d.ts +84 -3
  37. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  38. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  39. package/lib/types/core/verification.d.ts +386 -0
  40. package/lib/types/core/workspace-target.d.ts +41 -0
  41. package/lib/types/host/agent-tools.d.ts +1 -1
  42. package/lib/types/host/extension-registry.d.ts +111 -0
  43. package/lib/types/host/llm-dispatch.d.ts +63 -0
  44. package/lib/types/host/verification-gate.d.ts +71 -0
  45. package/lib/types/host/verification-runner.d.ts +108 -0
  46. package/lib/types/host/workspace-evidence.d.ts +53 -0
  47. package/lib/types/host-ledger.d.ts +52 -8
  48. package/lib/types/host-runner.d.ts +23 -1
  49. package/lib/types/host-service.d.ts +88 -2
  50. package/lib/types/index.d.ts +80 -26
  51. package/lib/types/protocol.d.ts +49 -5
  52. package/lib/types/tool-surface.d.ts +71 -0
  53. package/package.json +27 -26
  54. package/src/client/PluginSettingsCard.tsx +9 -0
  55. package/src/client/TaskBoardSettingsCard.tsx +304 -65
  56. package/src/client/board/ClampedMarkdown.tsx +69 -0
  57. package/src/client/board/ConfirmDialog.tsx +7 -3
  58. package/src/client/board/EditTaskModal.tsx +5 -2
  59. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  60. package/src/client/board/NewTaskModal.tsx +17 -1
  61. package/src/client/board/TagManagerModal.tsx +235 -0
  62. package/src/client/board/TaskBoard.tsx +259 -103
  63. package/src/client/board/TaskCard.tsx +214 -81
  64. package/src/client/board/TaskDetail.tsx +71 -26
  65. package/src/client/board/TaskForm.tsx +13 -3
  66. package/src/client/board/VerificationReport.tsx +203 -0
  67. package/src/client/board/card-view.ts +220 -0
  68. package/src/client/board/icons.tsx +89 -0
  69. package/src/client/board/overlay.tsx +189 -0
  70. package/src/client/board/status-key.ts +27 -0
  71. package/src/client/board/task-markdown.module.css +137 -0
  72. package/src/client/board/task-markdown.tsx +106 -0
  73. package/src/client/board-card-seat.ts +102 -0
  74. package/src/client/board-settings.module.css +66 -0
  75. package/src/client/board.module.css +488 -63
  76. package/src/client/index.ts +43 -5
  77. package/src/client/locales.ts +208 -4
  78. package/src/client/native-panel.tsx +18 -2
  79. package/src/client/seats.tsx +58 -0
  80. package/src/client/service.ts +94 -0
  81. package/src/client/settings-card.module.css +31 -5
  82. package/src/client/settings-form.ts +27 -1
  83. package/src/core/controller.ts +94 -3
  84. package/src/core/extension.ts +376 -0
  85. package/src/core/freeze-snapshot.ts +2 -2
  86. package/src/core/handover.ts +16 -1
  87. package/src/core/issue-form.ts +19 -0
  88. package/src/core/store.ts +16 -0
  89. package/src/core/tasks.ts +100 -6
  90. package/src/core/use-cases/task-parent.ts +3 -2
  91. package/src/core/use-cases/task-tag.ts +110 -0
  92. package/src/core/use-cases/task-update.ts +10 -4
  93. package/src/core/verification.ts +826 -0
  94. package/src/core/workspace-target.ts +59 -0
  95. package/src/host/agent-tools.ts +20 -18
  96. package/src/host/extension-registry.ts +509 -0
  97. package/src/host/llm-dispatch.ts +87 -0
  98. package/src/host/verification-gate.ts +331 -0
  99. package/src/host/verification-runner.ts +535 -0
  100. package/src/host/workspace-evidence.ts +176 -0
  101. package/src/host-ai.ts +15 -9
  102. package/src/host-ledger.ts +143 -32
  103. package/src/host-routes.ts +15 -2
  104. package/src/host-runner.ts +60 -11
  105. package/src/host-service.ts +253 -10
  106. package/src/index.ts +204 -16
  107. package/src/protocol.ts +102 -8
  108. package/src/tool-surface.ts +95 -0
@@ -25,7 +25,6 @@ export declare const zh: {
25
25
  'board.status.failed': string;
26
26
  'board.runs': string;
27
27
  'board.pending': string;
28
- 'board.updated': string;
29
28
  'board.created': string;
30
29
  'board.hostError': string;
31
30
  'board.hostError.notMounted': string;
@@ -106,6 +105,22 @@ export declare const zh: {
106
105
  'board.tagFilter': string;
107
106
  'board.tagFilterClear': string;
108
107
  'board.tagEmpty': string;
108
+ 'board.tagManage': string;
109
+ 'tags.title': string;
110
+ 'tags.hint': string;
111
+ 'tags.empty': string;
112
+ 'tags.usage': string;
113
+ 'tags.rename': string;
114
+ 'tags.renameLabel': string;
115
+ 'tags.delete': string;
116
+ 'tags.deleteTitle': string;
117
+ 'tags.deleteConfirm': string;
118
+ 'tags.deleteOk': string;
119
+ 'tags.save': string;
120
+ 'tags.mergeHint': string;
121
+ 'tags.renameFailed': string;
122
+ 'tags.deleteFailed': string;
123
+ 'tags.done': string;
109
124
  'detail.permissionPending': string;
110
125
  'detail.permissionConfirm': string;
111
126
  'detail.permissionConfirmed': string;
@@ -121,6 +136,8 @@ export declare const zh: {
121
136
  'new.archiveOriginal': string;
122
137
  'detail.prompt': string;
123
138
  'detail.description': string;
139
+ 'detail.expand': string;
140
+ 'detail.collapse': string;
124
141
  'detail.execution': string;
125
142
  'detail.noExecution': string;
126
143
  'detail.run': string;
@@ -144,6 +161,9 @@ export declare const zh: {
144
161
  'delete.cancel': string;
145
162
  'status.move.backlog': string;
146
163
  'status.move.todo': string;
164
+ 'status.move.running': string;
165
+ 'status.move.done': string;
166
+ 'status.move.failed': string;
147
167
  'exec.error.noWorkspace': string;
148
168
  'exec.error.promptRejected': string;
149
169
  'run.failed': string;
@@ -203,11 +223,14 @@ export declare const zh: {
203
223
  'exec.reuseSessionHint': string;
204
224
  'exec.goalRun': string;
205
225
  'exec.goalRunHint': string;
226
+ 'exec.skipVerification': string;
227
+ 'exec.skipVerificationHint': string;
206
228
  'detail.executionSettings': string;
207
229
  'exec.hint': string;
208
230
  'settings.title': string;
209
231
  'settings.description': string;
210
232
  'settings.enabled': string;
233
+ 'settings.enabledCardHint': string;
211
234
  'settings.enabledHint': string;
212
235
  'settings.announceToAgent': string;
213
236
  'settings.announceToAgentHint': string;
@@ -235,6 +258,65 @@ export declare const zh: {
235
258
  'settings.maxSubtaskDepth': string;
236
259
  'settings.maxSubtaskDepthHint': string;
237
260
  'settings.maxSubtaskDepthOption': string;
261
+ 'settings.goalVerificationTitle': string;
262
+ 'settings.goalVerification': string;
263
+ 'settings.goalVerificationCardHint': string;
264
+ 'settings.goalVerificationHint': string;
265
+ 'settings.goalVerificationModel': string;
266
+ 'settings.goalVerificationModelHint': string;
267
+ 'settings.goalVerificationModelInherit': string;
268
+ 'settings.goalVerificationModelInheritUnknown': string;
269
+ 'settings.goalVerificationEffort': string;
270
+ 'settings.goalVerificationEffortHint': string;
271
+ 'settings.goalVerificationEffortInherit': string;
272
+ 'settings.goalVerificationEffortInheritUnknown': string;
273
+ 'settings.goalVerificationResolved': string;
274
+ 'settings.goalVerificationResolvedModel': string;
275
+ 'settings.goalVerificationResolvedEffort': string;
276
+ 'settings.goalVerificationResolvedNoEffort': string;
277
+ 'settings.goalVerificationResolvedSource': string;
278
+ 'settings.goalVerificationSourceInherit': string;
279
+ 'settings.goalVerificationSourceExplicit': string;
280
+ 'settings.goalVerificationResolvedPreset': string;
281
+ 'settings.goalVerificationRouteMissing': string;
282
+ 'settings.goalVerificationEffortFallback': string;
283
+ 'settings.goalVerificationEffortFallbackNone': string;
284
+ 'settings.goalVerificationModelInvalid': string;
285
+ 'running.executing': string;
286
+ 'running.verifying': string;
287
+ 'running.repairing': string;
288
+ 'running.verificationPassed': string;
289
+ 'running.verificationFailed': string;
290
+ 'verify.title': string;
291
+ 'verify.status.passed': string;
292
+ 'verify.status.failed': string;
293
+ 'verify.status.exception': string;
294
+ 'verify.status.verifying': string;
295
+ 'verify.status.pending': string;
296
+ 'verify.status.off': string;
297
+ 'verify.summary': string;
298
+ 'verify.criteria': string;
299
+ 'verify.criterion': string;
300
+ 'verify.rounds': string;
301
+ 'verify.judge': string;
302
+ 'verify.judgeNoEffort': string;
303
+ 'verify.judgeInherited': string;
304
+ 'verify.attempt': string;
305
+ 'verify.attemptException': string;
306
+ 'verify.counts': string;
307
+ 'verify.findings': string;
308
+ 'verify.noFindings': string;
309
+ 'verify.evidence': string;
310
+ 'verify.workspaceEvidence': string;
311
+ 'verify.usage': string;
312
+ 'verify.usageIncomplete': string;
313
+ 'verify.finalFailure': string;
314
+ 'verify.applicability.disabled': string;
315
+ 'verify.applicability.goalUnavailable': string;
316
+ 'verify.applicability.skipped': string;
317
+ 'verify.applicability.teamMember': string;
318
+ 'verify.effortFallback': string;
319
+ 'verify.thresholdValue': string;
238
320
  'detail.parent': string;
239
321
  'detail.parent.open': string;
240
322
  'detail.subtasks': string;
@@ -258,6 +340,25 @@ export declare const zh: {
258
340
  'card.subtasksFailed': string;
259
341
  'card.subtasksRunning': string;
260
342
  'card.subtasksBreakdown': string;
343
+ 'card.result.succeeded': string;
344
+ 'card.result.failed': string;
345
+ 'card.result.cancelled': string;
346
+ 'card.declared': string;
347
+ 'card.declaredHint': string;
348
+ 'card.openSession': string;
349
+ 'card.tagsLabel': string;
350
+ 'card.time.created': string;
351
+ 'card.time.started': string;
352
+ 'card.time.settled': string;
353
+ 'card.time.archived': string;
354
+ 'card.time.updated': string;
355
+ 'board.density.compact': string;
356
+ 'board.density.comfortable': string;
357
+ 'board.group.today': string;
358
+ 'board.group.week': string;
359
+ 'board.group.earlier': string;
360
+ 'board.group.expand': string;
361
+ 'board.group.collapse': string;
261
362
  'board.hideSubtasks': string;
262
363
  'board.showSubtasks': string;
263
364
  'board.subtaskFilterHint': string;
@@ -265,6 +366,7 @@ export declare const zh: {
265
366
  /** en dictionary, complete against the zh key set. */
266
367
  export declare const en: Record<keyof typeof zh, string>;
267
368
  /** The dictionary key union. */
369
+ /** The dictionary key union. */
268
370
  export type TaskBoardKey = keyof typeof zh;
269
371
  /** The settings-card slice of the task-board dictionary. */
270
372
  export type SettingsCardKey = TaskBoardKey;
@@ -1,23 +1,7 @@
1
- /**
2
- * Native panel registration for the task board.
3
- *
4
- * The board is an official-style center-column panel, not a DOM takeover: it
5
- * contributes a row into the sidebar shell's own global panel list
6
- * (`sidebar.panellist`) and its page into the layout's keyed `main` slot. The
7
- * shell then owns the row box, the label, the active highlight, the collapsed
8
- * rail, the panel switch and the window-chrome interplay exactly as it does for
9
- * the shipped Plugins and Schedule pages, so the board renders through the same
10
- * container the desktop application's Plugins page uses.
11
- *
12
- * Both registrations go through `ctx.slots.inject`, which fires only once the
13
- * owning shell entry has declared the seat: load order between this plugin and
14
- * ui-layout / ui-sidebar therefore does not matter, and neither does a shell
15
- * that cannot serve the seats (the callback simply never runs).
16
- *
17
- * @module @linxin666/dsh-client-ui-task-board/client
18
- */
1
+ import type { ReactNode } from 'react';
19
2
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
20
3
  import { TASK_BOARD_PANEL_ID, type BoardController } from '../core/controller.ts';
4
+ import { type SlotRenderBinding } from './seats.tsx';
21
5
  /** The panel id shared by the sidebar row and the main-slot occupant. */
22
6
  export { TASK_BOARD_PANEL_ID };
23
7
  /**
@@ -45,9 +29,11 @@ export declare function TaskBoardPanelIcon({ size }: {
45
29
  * @param props - the framework main-slot share plus this entry's injected face.
46
30
  * @returns the board page.
47
31
  */
48
- export declare function TaskBoardPanel({ controller }: {
32
+ export declare function TaskBoardPanel({ controller, renderSlot }: {
49
33
  controller: BoardController;
50
- }): React.ReactElement;
34
+ /** Child-slot renderer the registration declares (detail + card decoration). */
35
+ renderSlot?: SlotRenderBinding;
36
+ }): ReactNode;
51
37
  /**
52
38
  * Register the board's sidebar row and center-column page.
53
39
  *
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type TaskBoardCardDecorationProps, type TaskBoardDetailSectionProps, type TaskBoardSettingsSectionProps } from '../core/extension.ts';
3
+ /** The three provider seats, adapted to plain render functions. */
4
+ export interface TaskBoardSeats {
5
+ detailSection: (props: TaskBoardDetailSectionProps) => ReactNode;
6
+ settingsSection: (props: TaskBoardSettingsSectionProps) => ReactNode;
7
+ cardDecoration: (props: TaskBoardCardDecorationProps) => ReactNode;
8
+ }
9
+ /** Nothing registered: every seat renders empty. */
10
+ export declare const EMPTY_TASK_BOARD_SEATS: TaskBoardSeats;
11
+ /** The framework `renderSlot` binding shape (key + owner props share). */
12
+ export type SlotRenderBinding = (key: string, owner: Record<string, unknown>) => ReactNode;
13
+ /** Adapt one framework renderSlot binding to the board's seat interface. */
14
+ export declare function seatsFromRenderSlot(renderSlot: SlotRenderBinding | undefined): TaskBoardSeats;
15
+ /** Provider the board's registered components wrap their subtree in. */
16
+ export declare const TaskBoardSeatsProvider: import("react").Provider<TaskBoardSeats>;
17
+ /** Read the current seat set (empty when no board registration is above). */
18
+ export declare function useTaskBoardSeats(): TaskBoardSeats;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The board's browser-half extension service (`taskBoard`).
3
+ *
4
+ * It gives a provider's browser half the client capability face from the
5
+ * contract: dispatch over the board's same-origin action channel, card
6
+ * visibility predicates, and a subscription to the board mirror. The board
7
+ * controller is attached and detached as the master switch mounts and unmounts
8
+ * the UI, so the service can be provided before any controller exists.
9
+ *
10
+ * @module dsh-task-board/client/service
11
+ */
12
+ import type { BoardController } from '../core/controller.ts';
13
+ import type { TaskBoardClientFace, TaskBoardClientMirror, TaskBoardExtensionActionRequest, TaskBoardVisibilityPredicate } from '../core/extension.ts';
14
+ export declare class TaskBoardClientService implements TaskBoardClientFace {
15
+ private controller;
16
+ private enabled;
17
+ private unsubscribeController;
18
+ /** Predicates registered before the controller exists, replayed on attach. */
19
+ private readonly pendingVisibility;
20
+ private readonly listeners;
21
+ /** Bind the live board controller (called when the UI mounts). */
22
+ attach(controller: BoardController): void;
23
+ /** Release the current controller (called when the UI unmounts). */
24
+ detach(): void;
25
+ /** Apply the board's master switch. */
26
+ setEnabled(enabled: boolean): void;
27
+ dispatch(request: TaskBoardExtensionActionRequest): Promise<boolean>;
28
+ registerVisibility(predicate: TaskBoardVisibilityPredicate): () => void;
29
+ subscribe(callback: (mirror: TaskBoardClientMirror) => void): () => void;
30
+ snapshot(): TaskBoardClientMirror;
31
+ private notify;
32
+ }
@@ -134,6 +134,8 @@ export declare class CardForm<T> {
134
134
  private readonly disposeForm;
135
135
  private disposed;
136
136
  private saving;
137
+ /** A save asked for while one was in flight; it runs once the first settles (#1754). */
138
+ private saveQueued;
137
139
  private failed;
138
140
  private failedReason;
139
141
  /** @param scope - the bound configuration form for this card's namespace. */
@@ -167,6 +169,20 @@ export declare class CardForm<T> {
167
169
  * the user can correct them instead of retyping.
168
170
  * @returns settlement after the mutation and the read-back.
169
171
  */
172
+ /**
173
+ * Run one save, and re-run it once if another was asked for while this one
174
+ * was still in flight.
175
+ *
176
+ * A save is a Host round trip that also drives the profile reconcile, and
177
+ * the Host runs that write inside one exclusive transaction. Answering a
178
+ * press that arrives mid-flight by returning immediately dropped the edit
179
+ * with no explanation, which is the "the save button stops working after a
180
+ * few rounds" report (#1754). Serializing instead means a save pressed while
181
+ * another is still settling runs against the settled state - which is what
182
+ * the operator meant by pressing it again.
183
+ * @returns settlement after the mutation and the read-back.
184
+ */
185
+ requestSave(): Promise<void>;
170
186
  save(): Promise<void>;
171
187
  /**
172
188
  * Every staged edit a save would write. An entry whose draft is not a value
@@ -15,6 +15,7 @@ import type { TaskStore } from './store.ts';
15
15
  import { type NewTaskInput, type TaskRecord, type TaskStatus } from './tasks.ts';
16
16
  import { type TaskUpdatePatch } from './use-cases/task-update.ts';
17
17
  import type { TaskBoardAction, TaskBoardEventPayload, TaskBoardParseDraft, TaskBoardParseRequest, TaskBoardSnapshot } from '../protocol.ts';
18
+ import type { TaskBoardExtensionActionRequest, TaskBoardVisibilityPredicate } from './extension.ts';
18
19
  export interface TaskBoardTransport {
19
20
  bootstrap(legacy: readonly TaskRecord[]): Promise<TaskBoardSnapshot>;
20
21
  state(): Promise<TaskBoardSnapshot>;
@@ -110,7 +111,7 @@ export interface ExecutionOptionsSnapshot {
110
111
  * (revision/scheduler/power); a full snapshot also carries the deployment
111
112
  * constants the UI reads (session default permission, subtask depth).
112
113
  */
113
- export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable'>;
114
+ export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable' | 'extensions'>;
114
115
  /** Immutable controller snapshot for UI subscriptions. */
115
116
  export interface ControllerSnapshot {
116
117
  tasks: readonly TaskRecord[];
@@ -127,6 +128,15 @@ export interface ControllerSnapshot {
127
128
  canParseTask?: boolean;
128
129
  transportError?: string;
129
130
  host?: HostMirror;
131
+ /**
132
+ * Provider card-visibility predicates, in registration order. A task shows
133
+ * only while every predicate accepts it; providers hide their own off-board
134
+ * cards here instead of the board hard-coding a provider rule. Optional so a
135
+ * hand-built snapshot (tests, a shell-less composition) defaults to visible.
136
+ */
137
+ visibility?: readonly TaskBoardVisibilityPredicate[];
138
+ /** Read-only summaries published by running extensions, keyed by extension id. */
139
+ extensions?: Readonly<Record<string, unknown>>;
130
140
  }
131
141
  /** The selected task (resolved from the ledger), or undefined. */
132
142
  export declare function selectedTaskOf(snapshot: ControllerSnapshot): TaskRecord | undefined;
@@ -150,6 +160,8 @@ export declare class BoardController {
150
160
  private readonly taskQueues;
151
161
  private transportError;
152
162
  private hostState;
163
+ /** Provider visibility predicates (immutable array; replaced on change). */
164
+ private visibility;
153
165
  private remoteSubscribed;
154
166
  private remoteInitialization;
155
167
  /** @param deps - store and the sessions navigation face. */
@@ -159,6 +171,16 @@ export declare class BoardController {
159
171
  /** Stop all subscriptions and drop retained state (idempotent). */
160
172
  dispose(): void;
161
173
  getSnapshot(): ControllerSnapshot;
174
+ /**
175
+ * Register one card-visibility predicate. The returned disposer removes it;
176
+ * both changes notify subscribers so the board re-renders.
177
+ */
178
+ registerVisibility(predicate: TaskBoardVisibilityPredicate): () => void;
179
+ /**
180
+ * Deliver one provider action through the board's same-origin channel. A
181
+ * board without a Host transport (the legacy in-memory path) refuses it.
182
+ */
183
+ dispatchExtension(request: TaskBoardExtensionActionRequest): Promise<boolean>;
162
184
  subscribe(fn: () => void): () => void;
163
185
  /** Whether production mutations are confirmed by the Host transport. */
164
186
  isHostBacked(): boolean;
@@ -250,6 +272,20 @@ export declare class BoardController {
250
272
  setParent(id: string, parentId: string | null): Promise<boolean>;
251
273
  /** Restore an archived task back onto the board (same status column). */
252
274
  restoreTask(id: string): boolean;
275
+ /**
276
+ * Rename one label across the whole ledger; a name already in use merges the
277
+ * two labels. Host-backed: the ledger owns the transition and confirms it, so
278
+ * the board's label row reflects whether the Host accepted the edit.
279
+ * @returns true when the edit was accepted by the authority.
280
+ */
281
+ renameTag(from: string, to: string): Promise<boolean>;
282
+ /**
283
+ * Remove one label from every task that carries it, board and archive alike —
284
+ * the board's label row is drawn from the whole ledger, so a label left on an
285
+ * archived card would come straight back.
286
+ * @returns true when the edit was accepted by the authority.
287
+ */
288
+ deleteTag(name: string): Promise<boolean>;
253
289
  /**
254
290
  * Update a task's schedule rule. A blank or invalid cron expression is
255
291
  * rejected (returns false, state untouched). When the rule ends up enabled
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The task board's external provider extension contract.
3
+ *
4
+ * This module is the single source of truth for how another plugin adds a
5
+ * provider surface to the task board: the cordis service name both halves
6
+ * publish, the API version they negotiate, the child seats a provider renders
7
+ * into, the host capability face a provider implements against, and the client
8
+ * capability face the board's browser half provides. It is framework-free and
9
+ * shared by the host and browser programs; the only imported SDK type is
10
+ * type-only, so the browser bundle never pulls a value out of an SDK package.
11
+ *
12
+ * @module dsh-task-board/core/extension
13
+ */
14
+ import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
15
+ import type { ExecutionOutcome, TaskRecord, TaskStatus } from './tasks.ts';
16
+ /** Cordis service name the host and browser halves each publish. */
17
+ export declare const TASK_BOARD_SERVICE_NAME = "taskBoard";
18
+ /**
19
+ * Contract version of {@link TASK_BOARD_SERVICE_NAME}. A provider declares the
20
+ * version it was built against; the board refuses (visibly) any other version
21
+ * instead of guessing at a stale shape.
22
+ */
23
+ export declare const TASK_BOARD_API_VERSION = 1;
24
+ /** Child seat a provider registers its task-detail section into. */
25
+ export declare const TASK_BOARD_DETAIL_SECTION = "task-board.detail.section";
26
+ /** Child seat a provider registers its settings section into. */
27
+ export declare const TASK_BOARD_SETTINGS_SECTION = "task-board.settings.section";
28
+ /** Child seat a provider registers its card decoration into. */
29
+ export declare const TASK_BOARD_CARD_DECORATION = "task-board.card.decoration";
30
+ /** Every child seat the board declares for extensions, in render order. */
31
+ export declare const TASK_BOARD_EXTENSION_SEATS: readonly string[];
32
+ /**
33
+ * Largest accepted extension payload, in bytes of its JSON serialization. One
34
+ * integration entry — and one action payload — may carry schema the board does
35
+ * not interpret, but never enough of it to bloat the ledger or the action
36
+ * channel.
37
+ */
38
+ export declare const TASK_BOARD_PAYLOAD_LIMIT_BYTES: number;
39
+ /** One opaque provider payload the board stores and forwards without interpreting. */
40
+ export type TaskBoardExtensionPayload = Record<string, unknown>;
41
+ /**
42
+ * Validate one extension payload: a pure JSON object whose serialization fits
43
+ * {@link TASK_BOARD_PAYLOAD_LIMIT_BYTES}. Arrays, primitives and non-plain
44
+ * objects are refused; the board never interprets the keys.
45
+ * @param value - candidate payload.
46
+ * @returns whether the value is an acceptable opaque payload.
47
+ */
48
+ export declare function isTaskBoardExtensionPayload(value: unknown): value is TaskBoardExtensionPayload;
49
+ /** Byte length of a value's JSON serialization, or undefined when it does not serialize. */
50
+ export declare function taskBoardPayloadBytes(value: unknown): number | undefined;
51
+ /**
52
+ * Normalize a task's whole integrations container: a plain JSON object whose
53
+ * values are each an opaque provider payload within the size limit. A malformed
54
+ * container normalizes to undefined (the field is dropped), and one malformed
55
+ * entry is dropped alone so a single bad provider can never take a card — or
56
+ * another provider's data — with it.
57
+ * @param value - candidate container from the wire, the ledger, or a create input.
58
+ * @returns the cleaned container, or undefined when nothing usable remains.
59
+ */
60
+ export declare function normalizeTaskIntegrations(value: unknown): Record<string, TaskBoardExtensionPayload> | undefined;
61
+ /** One action a provider dispatches over the board's same-origin channel. */
62
+ export interface TaskBoardExtensionActionRequest {
63
+ /** Id of the extension the action belongs to. */
64
+ extensionId: string;
65
+ /** Provider-owned action name, routed back to that extension's handleAction. */
66
+ action: string;
67
+ /** Task the action targets, when it targets one. */
68
+ taskId?: string;
69
+ /** Opaque provider payload, bounded like every other extension payload. */
70
+ payload?: TaskBoardExtensionPayload;
71
+ }
72
+ /**
73
+ * Deliver one extension action through the board's same-origin action channel.
74
+ * The extension never opens an HTTP surface of its own.
75
+ */
76
+ export type TaskBoardExtensionDispatch = (request: TaskBoardExtensionActionRequest) => Promise<boolean>;
77
+ /** Draft one provider creates a task from. */
78
+ export interface TaskBoardTaskDraft {
79
+ title: string;
80
+ description: string;
81
+ prompt: string;
82
+ /** Column the created card opens in; absent means the board default (todo). */
83
+ status?: TaskStatus;
84
+ /** Parent card id, when the provider materializes a subtask. */
85
+ parentId?: string;
86
+ /**
87
+ * Workspace the execution must run in. Absent leaves the board's own
88
+ * inheritance rules in charge (the creating session's workspace for a root
89
+ * card, else the recent workspace at execution time), so a provider that has
90
+ * no reason to know better pins nothing.
91
+ */
92
+ workspaceId?: string;
93
+ }
94
+ /** Options of {@link TaskBoardTasksFace.create}. */
95
+ export interface TaskBoardCreateOptions {
96
+ /** Opaque payload stored under the creating extension's id. */
97
+ payload?: TaskBoardExtensionPayload;
98
+ /** Create the card off-board: it exists in the ledger but is never shown on the board. */
99
+ hidden?: boolean;
100
+ }
101
+ /** One task linked to an extension, with that extension's own payload. */
102
+ export interface TaskBoardLinkedTask {
103
+ task: TaskRecord;
104
+ payload: TaskBoardExtensionPayload;
105
+ }
106
+ /** Task reads and writes the board grants an extension. */
107
+ export interface TaskBoardTasksFace {
108
+ /** Every task in the ledger, most recent mutation last. */
109
+ list(): readonly TaskRecord[];
110
+ /** One task by id, or undefined. */
111
+ get(taskId: string): TaskRecord | undefined;
112
+ /**
113
+ * Create a task through the board's own creation gate; the payload lands
114
+ * under the creating extension's id. Throws on a refused draft.
115
+ */
116
+ create(draft: TaskBoardTaskDraft, options?: TaskBoardCreateOptions): TaskRecord;
117
+ /**
118
+ * Patch a task's content. Reuses the board's own content gate: a task that
119
+ * has started executing keeps its recorded title/description/prompt.
120
+ * Throws when the card is frozen.
121
+ */
122
+ patchContent(taskId: string, patch: {
123
+ title?: string;
124
+ description?: string;
125
+ prompt?: string;
126
+ }): void;
127
+ /**
128
+ * Move a task through the board's existing status gates (running lock,
129
+ * permission confirmation gate). Throws on refusal.
130
+ */
131
+ setStatus(taskId: string, status: TaskStatus, initiator?: string): void;
132
+ /** Every task carrying this extension's payload. */
133
+ linked(): readonly TaskBoardLinkedTask[];
134
+ }
135
+ /** Storage face for one extension's own payload on a task. */
136
+ export interface TaskBoardIntegrationFace {
137
+ /** Read this extension's payload, or undefined when the task carries none. */
138
+ read(taskId: string): TaskBoardExtensionPayload | undefined;
139
+ /**
140
+ * Shallow-merge this extension's payload into the task: the given keys
141
+ * replace the stored ones, and a key explicitly set to undefined clears it.
142
+ * The board stores the result opaquely; a non-JSON or oversized payload is
143
+ * refused.
144
+ */
145
+ write(taskId: string, payload: TaskBoardExtensionPayload): void;
146
+ }
147
+ /** Status-change notification. */
148
+ export interface TaskBoardStatusChangedEvent {
149
+ taskId: string;
150
+ status: TaskStatus;
151
+ previous: TaskStatus;
152
+ initiator?: string;
153
+ }
154
+ /** Execution-settlement notification. */
155
+ export interface TaskBoardExecutionSettledEvent {
156
+ taskId: string;
157
+ executionId: string;
158
+ outcome: ExecutionOutcome;
159
+ error?: string;
160
+ }
161
+ /** Task-deletion notification. */
162
+ export interface TaskBoardTaskDeletedEvent {
163
+ taskId: string;
164
+ }
165
+ /**
166
+ * Event subscriptions an extension registers for. Every callback runs isolated:
167
+ * a throw is logged and never interrupts the board's own execution path.
168
+ */
169
+ export interface TaskBoardEventFace {
170
+ onStatusChanged(callback: (event: TaskBoardStatusChangedEvent) => void): () => void;
171
+ onExecutionSettled(callback: (event: TaskBoardExecutionSettledEvent) => void): () => void;
172
+ onTaskDeleted(callback: (event: TaskBoardTaskDeletedEvent) => void): () => void;
173
+ }
174
+ /**
175
+ * Host capability face handed to one extension while it is started. The face
176
+ * lives for the extension's enabled lifetime; the board revokes it by calling
177
+ * the extension's stop().
178
+ */
179
+ export interface TaskBoardExtensionHost {
180
+ tasks: TaskBoardTasksFace;
181
+ integration: TaskBoardIntegrationFace;
182
+ events: TaskBoardEventFace;
183
+ /**
184
+ * Publish this extension's read-only summary; it lands in the snapshot's
185
+ * extensions[id] for the extension's browser half to render.
186
+ */
187
+ publish(summary: TaskBoardExtensionPayload): void;
188
+ /** Register one model-visible tool; the returned disposer unregisters it. */
189
+ registerTool(definition: ToolDefinition): () => void;
190
+ }
191
+ /** One external provider contributed to the task board. */
192
+ export interface TaskBoardExtension {
193
+ /** Stable extension id; also the key of this extension's integration payload. */
194
+ readonly id: string;
195
+ /** Contract version this extension was built against. */
196
+ readonly apiVersion: number;
197
+ /** Volatile per-extension switch; absent means enabled. Read at use time. */
198
+ enabled?(): boolean;
199
+ /** Acquire the capability face and start any provider-side work. */
200
+ start?(host: TaskBoardExtensionHost): void;
201
+ /** Release provider-side work. Never clears stored data. */
202
+ stop?(): void;
203
+ /** Route one dispatched action; the returned value goes back to the dispatcher. */
204
+ handleAction?(request: TaskBoardExtensionActionRequest): unknown | Promise<unknown>;
205
+ }
206
+ /** Board mirror an extension's browser half observes. */
207
+ export interface TaskBoardClientMirror {
208
+ /** Whether the board's master switch currently runs the extension faces. */
209
+ enabled: boolean;
210
+ /** Tasks the board currently holds. */
211
+ tasks: readonly TaskRecord[];
212
+ /** Published extension summaries, keyed by extension id (opaque to the board). */
213
+ extensions: Readonly<Record<string, unknown>>;
214
+ }
215
+ /** Whether a task should be shown on the board; false hides it. */
216
+ export type TaskBoardVisibilityPredicate = (task: TaskRecord) => boolean;
217
+ /**
218
+ * Client capability face the board's browser half provides. A provider's
219
+ * browser half resolves it from the shared cordis service name; it never
220
+ * value-imports the board package.
221
+ */
222
+ export interface TaskBoardClientFace {
223
+ /** Deliver one action through the board's same-origin action channel. */
224
+ dispatch(request: TaskBoardExtensionActionRequest): Promise<boolean>;
225
+ /** Register one card visibility predicate; the returned disposer removes it. */
226
+ registerVisibility(predicate: TaskBoardVisibilityPredicate): () => void;
227
+ /** Subscribe to the board mirror. */
228
+ subscribe(callback: (mirror: TaskBoardClientMirror) => void): () => void;
229
+ /** Read the current board mirror. */
230
+ snapshot(): TaskBoardClientMirror;
231
+ }
232
+ /** Owner props of the task-detail seat. */
233
+ export interface TaskBoardDetailSectionProps {
234
+ task: TaskRecord;
235
+ dispatch: TaskBoardExtensionDispatch;
236
+ }
237
+ /** Owner props of the settings seat. */
238
+ export interface TaskBoardSettingsSectionProps {
239
+ dispatch: TaskBoardExtensionDispatch;
240
+ }
241
+ /** Owner props of the card-decoration seat. */
242
+ export interface TaskBoardCardDecorationProps {
243
+ task: TaskRecord;
244
+ }
245
+ /**
246
+ * Host-half service face published under {@link TASK_BOARD_SERVICE_NAME}. A
247
+ * provider package's host half resolves this from the shared cordis service
248
+ * and registers its extension; the board's ledger never crosses this boundary.
249
+ */
250
+ export interface TaskBoardHostFace {
251
+ /**
252
+ * Admit one provider extension. Idempotent by extension id; the returned
253
+ * disposer releases it without clearing stored data.
254
+ */
255
+ registerExtension(extension: TaskBoardExtension): () => void;
256
+ /** Whether the board currently runs a given extension (board x extension). */
257
+ isExtensionEnabled(extensionId: string): boolean;
258
+ }
259
+ declare module '@deepseek-ai/cordis' {
260
+ interface Context {
261
+ /**
262
+ * The board's extension service. The host half publishes
263
+ * {@link TaskBoardHostFace}; the browser half publishes
264
+ * {@link TaskBoardClientFace} under the same name. The two halves never run
265
+ * in one process, so the browser half resolves its face through
266
+ * {@link resolveTaskBoardClientFace} and this shared declaration names the
267
+ * host face that registration-side consumers type against.
268
+ */
269
+ taskBoard: TaskBoardHostFace;
270
+ }
271
+ }
272
+ /** The narrow context seat {@link resolveTaskBoardClientFace} needs. */
273
+ export interface TaskBoardClientContextSeat {
274
+ get(name: string): unknown;
275
+ }
276
+ /**
277
+ * Resolve the browser half's client capability face by service name. Returns
278
+ * undefined when the board is not loaded or the service is not served, so a
279
+ * provider's browser half can degrade instead of throwing.
280
+ * @param ctx - client context.
281
+ * @returns the client face, or undefined.
282
+ */
283
+ export declare function resolveTaskBoardClientFace(ctx: TaskBoardClientContextSeat): TaskBoardClientFace | undefined;