@linxin666/dsh-client-ui-task-board 0.4.3 → 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 (128) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +56 -18
  3. package/README.zh.md +56 -18
  4. package/lib/client.js +6242 -958
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +3475 -234
  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 +23 -2
  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/preset-label.d.ts +22 -0
  24. package/lib/types/client/board/status-key.d.ts +15 -0
  25. package/lib/types/client/board/task-markdown.d.ts +19 -0
  26. package/lib/types/client/board-card-seat.d.ts +34 -0
  27. package/lib/types/client/host-api.d.ts +24 -1
  28. package/lib/types/client/index.d.ts +19 -0
  29. package/lib/types/client/locales.d.ts +141 -3
  30. package/lib/types/client/native-panel.d.ts +6 -20
  31. package/lib/types/client/schedule-zone.d.ts +47 -0
  32. package/lib/types/client/seats.d.ts +18 -0
  33. package/lib/types/client/service.d.ts +32 -0
  34. package/lib/types/client/settings-form.d.ts +16 -0
  35. package/lib/types/core/controller.d.ts +38 -1
  36. package/lib/types/core/degraded-reason.d.ts +46 -0
  37. package/lib/types/core/extension.d.ts +283 -0
  38. package/lib/types/core/handover.d.ts +13 -1
  39. package/lib/types/core/issue-form.d.ts +16 -0
  40. package/lib/types/core/package-name.d.ts +9 -0
  41. package/lib/types/core/schedule.d.ts +95 -20
  42. package/lib/types/core/subtask.d.ts +30 -11
  43. package/lib/types/core/tasks.d.ts +110 -5
  44. package/lib/types/core/use-cases/task-create.d.ts +2 -1
  45. package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
  46. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  47. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  48. package/lib/types/core/verification.d.ts +386 -0
  49. package/lib/types/core/workspace-target.d.ts +41 -0
  50. package/lib/types/host/agent-tools.d.ts +1 -1
  51. package/lib/types/host/extension-registry.d.ts +111 -0
  52. package/lib/types/host/llm-dispatch.d.ts +63 -0
  53. package/lib/types/host/verification-gate.d.ts +71 -0
  54. package/lib/types/host/verification-runner.d.ts +108 -0
  55. package/lib/types/host/workspace-evidence.d.ts +53 -0
  56. package/lib/types/host-ledger.d.ts +107 -11
  57. package/lib/types/host-runner.d.ts +99 -4
  58. package/lib/types/host-service.d.ts +114 -2
  59. package/lib/types/index.d.ts +80 -26
  60. package/lib/types/protocol.d.ts +53 -3
  61. package/lib/types/tool-surface.d.ts +71 -0
  62. package/package.json +27 -26
  63. package/src/client/PluginSettingsCard.tsx +9 -0
  64. package/src/client/TaskBoardSettingsCard.tsx +304 -65
  65. package/src/client/board/ClampedMarkdown.tsx +69 -0
  66. package/src/client/board/ConfirmDialog.tsx +7 -3
  67. package/src/client/board/EditTaskModal.tsx +5 -2
  68. package/src/client/board/LinkSubtaskModal.tsx +30 -15
  69. package/src/client/board/NewTaskModal.tsx +288 -151
  70. package/src/client/board/TagManagerModal.tsx +235 -0
  71. package/src/client/board/TaskBoard.tsx +259 -103
  72. package/src/client/board/TaskCard.tsx +238 -86
  73. package/src/client/board/TaskDetail.tsx +154 -59
  74. package/src/client/board/TaskForm.tsx +61 -6
  75. package/src/client/board/VerificationReport.tsx +203 -0
  76. package/src/client/board/card-view.ts +220 -0
  77. package/src/client/board/icons.tsx +89 -0
  78. package/src/client/board/overlay.tsx +189 -0
  79. package/src/client/board/preset-label.ts +44 -0
  80. package/src/client/board/status-key.ts +27 -0
  81. package/src/client/board/task-markdown.module.css +137 -0
  82. package/src/client/board/task-markdown.tsx +106 -0
  83. package/src/client/board-card-seat.ts +102 -0
  84. package/src/client/board-settings.module.css +66 -0
  85. package/src/client/board.module.css +602 -68
  86. package/src/client/host-api.ts +109 -6
  87. package/src/client/index.ts +48 -6
  88. package/src/client/locales.ts +290 -12
  89. package/src/client/native-panel.tsx +18 -2
  90. package/src/client/schedule-zone.ts +135 -0
  91. package/src/client/seats.tsx +58 -0
  92. package/src/client/service.ts +94 -0
  93. package/src/client/settings-card.module.css +31 -5
  94. package/src/client/settings-form.ts +27 -1
  95. package/src/core/controller.ts +101 -5
  96. package/src/core/degraded-reason.ts +81 -0
  97. package/src/core/extension.ts +376 -0
  98. package/src/core/freeze-snapshot.ts +2 -2
  99. package/src/core/handover.ts +16 -1
  100. package/src/core/issue-form.ts +19 -0
  101. package/src/core/package-name.ts +9 -0
  102. package/src/core/schedule.ts +211 -55
  103. package/src/core/store.ts +26 -1
  104. package/src/core/subtask.ts +77 -22
  105. package/src/core/tasks.ts +138 -10
  106. package/src/core/use-cases/task-create.ts +14 -2
  107. package/src/core/use-cases/task-parent.ts +3 -2
  108. package/src/core/use-cases/task-schedule.ts +17 -6
  109. package/src/core/use-cases/task-tag.ts +110 -0
  110. package/src/core/use-cases/task-update.ts +13 -4
  111. package/src/core/verification.ts +826 -0
  112. package/src/core/workspace-target.ts +59 -0
  113. package/src/host/agent-tools.ts +58 -24
  114. package/src/host/extension-registry.ts +509 -0
  115. package/src/host/llm-dispatch.ts +87 -0
  116. package/src/host/verification-gate.ts +331 -0
  117. package/src/host/verification-runner.ts +535 -0
  118. package/src/host/workspace-evidence.ts +176 -0
  119. package/src/host-ai.ts +15 -9
  120. package/src/host-ledger.ts +342 -51
  121. package/src/host-routes.ts +15 -2
  122. package/src/host-runner.ts +219 -20
  123. package/src/host-service.ts +346 -26
  124. package/src/index.ts +204 -16
  125. package/src/protocol.ts +122 -9
  126. package/src/tool-surface.ts +95 -0
  127. package/lib/types/host/run-guarded.d.ts +0 -39
  128. package/src/host/run-guarded.ts +0 -76
@@ -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;
@@ -35,7 +35,11 @@ export declare const HANDOVER_MAX_REFERENCE_BYTES = 512;
35
35
  export declare const HANDOVER_MAX_TOTAL_BYTES: number;
36
36
  /** Per target-id (workspace/preset) byte cap. */
37
37
  export declare const HANDOVER_MAX_TARGET_BYTES = 256;
38
- /** The board's notion of the deployment session-default permission (fail-safe default). */
38
+ /**
39
+ * Fail-safe baseline for the confirmation gate: what it falls back to when the
40
+ * deployment pins no baseline of its own and the Host's default permission
41
+ * preset cannot be read. Every elevation above it stays gated.
42
+ */
39
43
  export declare const DEFAULT_SESSION_PERMISSION: TaskPermission;
40
44
  /** Permission elevation rank (higher = more authority). */
41
45
  export declare const PERMISSION_RANK: ReadonlyMap<TaskPermission, number>;
@@ -51,6 +55,14 @@ export declare function handoverOf(input: TaskHandoverInput, now: number): TaskH
51
55
  export declare function effectivePermission(task: Pick<TaskRecord, 'permission' | 'handover'>): TaskPermission | undefined;
52
56
  /** Whether a binding's permission is elevated above the session default. */
53
57
  export declare function exceedsSessionDefault(permission: TaskPermission | undefined, sessionDefault: TaskPermission): boolean;
58
+ /**
59
+ * Whether a session running at `holder` already carries `permission`: the
60
+ * permission is absent, or its rank does not exceed the holder's. A teammate
61
+ * inherits the Lead session's permission and cannot be narrowed below it, so a
62
+ * subtask's own pin is carried by a team run exactly when this holds, and a pin
63
+ * it does not carry would be silently dropped instead.
64
+ */
65
+ export declare function permissionCarriedBy(holder: TaskPermission, permission: TaskPermission | undefined): boolean;
54
66
  /**
55
67
  * The confirmation-gate predicate: an elevated permission without a human
56
68
  * confirmation stamp. Manual run/rerun and cron must refuse such a card.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Issue-form vocabulary the card excerpt recognizes.
3
+ *
4
+ * Issue forms from an issue tracker (and the zh templates this family's repositories use)
5
+ * write the same section headings and empty-field placeholders on every issue.
6
+ * The patterns are data about those forms, not UI copy, so they live in the
7
+ * shared core rather than in a locale dictionary; the browser half imports them.
8
+ *
9
+ * @module @linxin666/dsh-client-ui-task-board/core/issue-form
10
+ */
11
+ /** Section headings whose body is the issue's own summary (zh and en forms). */
12
+ export declare const SUMMARY_HEADING: RegExp;
13
+ /** Values an issue form writes for an empty field. */
14
+ export declare const EMPTY_FIELD: RegExp;
15
+ /** A trailing colon (ASCII or full-width) after a heading. */
16
+ export declare const HEADING_COLON: RegExp;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * npm identity of this plugin. The dsh-web-all degraded ledger is keyed by the
3
+ * real plugin package name of a family row, and this plugin's browser half
4
+ * must name its OWN row there to learn why its Host API is missing (issue
5
+ * #1730): the served bundle cannot derive the name, because a family row
6
+ * mounts the package from a versioned profile path. The host half spells the
7
+ * same name in its single-mount guard (src/index.ts); the two move together.
8
+ */
9
+ export declare const TASK_BOARD_PACKAGE = "@linxin666/dsh-client-ui-task-board";
@@ -1,15 +1,36 @@
1
1
  /**
2
- * Minimal 5-field cron parsing and next-run computation for scheduled task
3
- * runs. Framework-free and dependency-free so the scheduler and controller
4
- * share one tiny pure module.
2
+ * Zone-aware 5-field cron parsing and next-run computation for scheduled task
3
+ * runs. Framework-free and dependency-free (Intl only) so the Host scheduler
4
+ * and the browser next-run preview share one tiny pure module.
5
5
  *
6
6
  * Grammar: five whitespace-separated fields, 分 时 日 月 周. Every field
7
7
  * supports the wildcard, step (wildcard or range + "/n"), single value,
8
8
  * inclusive range a-b, and comma lists mixing any of those. Ranges: minutes
9
9
  * 0-59, hours 0-23, days 1-31, months 1-12, weekdays 0-7 (0 and 7 both mean
10
- * Sunday). When both the day and weekday fields are restricted they combine
11
- * with OR semantics (standard cron). Invalid expressions parse to null and
12
- * are rejected by the UI/controller.
10
+ * Sunday). Invalid expressions parse to null and are rejected by the UI and
11
+ * the Host.
12
+ *
13
+ * Day semantics follow Vixie cron: a field is *starred* when its field text
14
+ * starts with `*`. When day-of-month and day-of-week are both restricted the
15
+ * fields combine with OR; otherwise they combine with AND. The distinction
16
+ * matters for a stepped star such as `*&#47;2`, which still restricts the dates it
17
+ * matches, so `0 0 *&#47;2 * 1` means "the Mondays that fall on an even day of
18
+ * month" rather than "every even day, or every Monday".
19
+ *
20
+ * Time zone: every candidate wall clock is interpreted in an explicit IANA
21
+ * zone (the schedule's stored zone, or the Host zone when none is given).
22
+ * A wall clock that does not exist (spring-forward gap) is skipped, and an
23
+ * ambiguous wall clock (fall-back overlap) resolves to the earlier instant, so
24
+ * a repeated local minute fires exactly once. The Host-scheduler semantics are
25
+ * byte-identical to `@deepseek-ai/dsh-schedule`'s resolver, which was verified
26
+ * against it across twelve IANA zones (including 30-minute and 45-minute DST
27
+ * offsets and a date-line jump) and 300k decision instants.
28
+ *
29
+ * That upstream is on the experimental track and is NOT a dependency here (no
30
+ * import, no inject, no peer range), so a change to it fails no test. Re-run the
31
+ * differential check on any `dsh-schedule` release or cohort bump that moves it,
32
+ * and keep this comment honest; the re-verification trigger is recorded in
33
+ * [the owning Agent Note](../../../../.agents/notes/implemented/feature/2026-09-29-task-board-schedule-time-zone.md).
13
34
  */
14
35
  /** The parsed match sets of one cron expression. */
15
36
  export interface CronSchedule {
@@ -19,11 +40,61 @@ export interface CronSchedule {
19
40
  months: ReadonlySet<number>;
20
41
  /** Weekdays 0-6, 0 = Sunday (input 7 normalized to 0). */
21
42
  weekdays: ReadonlySet<number>;
22
- /** Whether the day-of-month field was the literal '*' (unrestricted). */
23
- dayWildcard: boolean;
24
- /** Whether the weekday field was the literal '*' (unrestricted). */
25
- weekdayWildcard: boolean;
43
+ /** Whether the day-of-month field text starts with `*` (Vixie starred). */
44
+ dayStarred: boolean;
45
+ /** Whether the weekday field text starts with `*` (Vixie starred). */
46
+ weekdayStarred: boolean;
47
+ }
48
+ /** Fallback zone when the process reports no usable IANA zone. */
49
+ export declare const HOST_TIME_ZONE_FALLBACK = "UTC";
50
+ /** One resolved wall-clock reading in some zone. */
51
+ export interface ZonedParts {
52
+ year: number;
53
+ month: number;
54
+ day: number;
55
+ hour: number;
56
+ minute: number;
26
57
  }
58
+ /**
59
+ * The wall-clock reading of one instant in an explicit zone. Reading through
60
+ * Intl (rather than the process `Date` accessors) is what makes the result
61
+ * independent of the Host's own TZ.
62
+ * @param timeZone - explicit IANA zone.
63
+ * @param epochMs - instant to read.
64
+ * @returns the zone-local calendar and clock fields.
65
+ */
66
+ export declare function partsInZone(timeZone: string, epochMs: number): ZonedParts;
67
+ /**
68
+ * Resolve a wall clock in an explicit zone to its instant.
69
+ *
70
+ * The offset is probed a day either side of the naive guess so both DST
71
+ * transitions are covered, then every candidate offset whose inverse maps back
72
+ * to the requested wall clock is kept. No survivor means the wall clock does
73
+ * not exist (a spring-forward gap); several means it is ambiguous (a fall-back
74
+ * overlap), and the earliest instant wins so the repeated clock is taken once.
75
+ * @param timeZone - explicit IANA zone.
76
+ * @param year - full calendar year.
77
+ * @param month - 1-12.
78
+ * @param day - day of month.
79
+ * @param hour - 0-23.
80
+ * @param minute - 0-59.
81
+ * @returns the instant in ms epoch, or undefined when the wall clock is skipped.
82
+ */
83
+ export declare function zonedEpoch(timeZone: string, year: number, month: number, day: number, hour: number, minute: number): number | undefined;
84
+ /**
85
+ * The Host process's IANA zone, used for schedules that store no zone of their
86
+ * own (a ledger written before zones were persisted).
87
+ * @returns a zone name Intl accepts, never empty.
88
+ */
89
+ export declare function resolveHostTimeZone(): string;
90
+ /**
91
+ * Whether a value names a zone this runtime can actually resolve. A stored
92
+ * zone is trusted only after it round-trips through Intl, so a typo or a zone
93
+ * that this ICU build does not know can never arm a schedule.
94
+ * @param value - candidate zone name.
95
+ * @returns true when the name is usable.
96
+ */
97
+ export declare function isValidTimeZone(value: string): boolean;
27
98
  /**
28
99
  * Parse a 5-field cron expression.
29
100
  * @returns the match sets, or null when the expression is invalid.
@@ -32,18 +103,22 @@ export declare function parseCron(expr: string): CronSchedule | null;
32
103
  /** Whether the expression parses. */
33
104
  export declare function isValidCron(expr: string): boolean;
34
105
  /**
35
- * Compute the next matching instant after `fromMs` (ms epoch), in local time,
36
- * at minute granularity, strictly greater than `fromMs`. Returns the ms epoch
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.
106
+ * Compute the next matching instant after `fromMs` strictly greater than it,
107
+ * interpreting the expression's wall clock in `timeZone` (the Host zone when
108
+ * omitted). Returns the ms epoch of the matching minute's start, or undefined
109
+ * when the calendar constraint can never match (for example `0 0 30 2 *`).
110
+ * The five-year horizon includes a full leap cycle, so a valid February 29
111
+ * schedule remains reachable from every non-leap year.
41
112
  *
42
113
  * Walks candidate year/month/day/hour/minute values straight from the parsed
43
114
  * field sets instead of scanning every minute: a sparse expression such as
44
115
  * `0 0 29 2 *` used to iterate ~1.5M wall-clock minutes before reaching the
45
- * next leap day. Wall-clock field construction + the final `matches` re-check
46
- * preserve the old minute scan's DST semantics exactly (nonexistent spring
47
- * minutes normalize forward and the repeated fall-back hour is never visited).
116
+ * next leap day. A candidate wall clock that the zone skips (spring-forward
117
+ * gap) is passed over, which is what makes the gap semantics fall out of the
118
+ * candidate walk rather than needing a separate rule.
119
+ * @param expr - 5-field cron expression.
120
+ * @param fromMs - exclusive lower bound, ms epoch.
121
+ * @param timeZone - explicit IANA zone; defaults to the Host zone.
122
+ * @returns the next trigger instant, or undefined when unreachable.
48
123
  */
49
- export declare function nextRunAtMs(expr: string, fromMs: number): number | undefined;
124
+ export declare function nextRunAtMs(expr: string, fromMs: number, timeZone?: string): number | undefined;
@@ -24,28 +24,40 @@ export declare const DEFAULT_SUBTASK_DEPTH = 1;
24
24
  export declare function normalizeSubtaskDepth(value: unknown): number;
25
25
  /** Whether an unknown value is a settled execution outcome. */
26
26
  export declare function isExecutionOutcome(value: unknown): value is ExecutionOutcome;
27
+ /**
28
+ * Reusable lineage index over ONE task list: the id lookup and the direct
29
+ * children per parent. A caller that runs several lineage queries over the same
30
+ * ledger (the link-subtask candidate gate runs one per task) builds this once
31
+ * instead of rescanning the list per query.
32
+ */
33
+ export interface LineageIndex {
34
+ readonly byId: ReadonlyMap<string, TaskRecord>;
35
+ readonly childrenByParent: ReadonlyMap<string, readonly TaskRecord[]>;
36
+ }
37
+ /** Build the reusable index for a task list (ledger order preserved). */
38
+ export declare function buildLineageIndex(tasks: readonly TaskRecord[]): LineageIndex;
27
39
  /**
28
40
  * Nearest-first ancestor chain of a task. The walk is cycle-safe and bounded
29
41
  * by the ledger size rather than by the deployment maximum: a chain stored
30
42
  * under a larger limit survives that limit being lowered, and restore and
31
43
  * inheritance must still see all of it.
32
44
  */
33
- export declare function ancestorChain(tasks: readonly TaskRecord[], task: TaskRecord): TaskRecord[];
45
+ export declare function ancestorChain(tasks: readonly TaskRecord[], task: TaskRecord, lineage?: LineageIndex): TaskRecord[];
34
46
  /** Root-to-task depth: a root task is 0, its subtask 1, and so on. */
35
- export declare function taskDepth(tasks: readonly TaskRecord[], id: string): number;
47
+ export declare function taskDepth(tasks: readonly TaskRecord[], id: string, lineage?: LineageIndex): number;
36
48
  /** Direct subtasks of a task, in ledger order. */
37
- export declare function directSubtasks(tasks: readonly TaskRecord[], id: string): TaskRecord[];
49
+ export declare function directSubtasks(tasks: readonly TaskRecord[], id: string, lineage?: LineageIndex): TaskRecord[];
38
50
  /**
39
51
  * Descendants of a task in breadth-first order, bounded by maxSubtaskDepth
40
52
  * total depth. The visited set keeps a malformed ledger from looping.
41
53
  */
42
- export declare function descendantTasks(tasks: readonly TaskRecord[], id: string, maxSubtaskDepth: number): TaskRecord[];
54
+ export declare function descendantTasks(tasks: readonly TaskRecord[], id: string, maxSubtaskDepth: number, lineage?: LineageIndex): TaskRecord[];
43
55
  /**
44
56
  * Height of a task's own subtree (0 for a leaf), bounded by the hard maximum.
45
57
  * The visited set makes the walk terminate on a cyclic hand-edited ledger
46
58
  * instead of recursing until the stack overflows.
47
59
  */
48
- export declare function subtreeHeight(tasks: readonly TaskRecord[], id: string, visited?: ReadonlySet<string>): number;
60
+ export declare function subtreeHeight(tasks: readonly TaskRecord[], id: string, visited?: ReadonlySet<string>, lineage?: LineageIndex): number;
49
61
  /** Why a parent link was refused; also the source of the Host error text. */
50
62
  export type ParentLinkRejection = 'unknown-task' | 'unknown-parent' | 'archived-parent' | 'self-parent' | 'cycle' | 'depth-exceeded';
51
63
  export interface ParentLinkCheck {
@@ -61,7 +73,7 @@ export interface ParentLinkCheck {
61
73
  * and the resulting depth (parent depth + 1 + the child own subtree height)
62
74
  * must stay within maxSubtaskDepth.
63
75
  */
64
- export declare function checkParentLink(tasks: readonly TaskRecord[], childId: string, parentId: string | null, maxSubtaskDepth: number): ParentLinkCheck;
76
+ export declare function checkParentLink(tasks: readonly TaskRecord[], childId: string, parentId: string | null, maxSubtaskDepth: number, lineage?: LineageIndex): ParentLinkCheck;
65
77
  /**
66
78
  * The tasks one run request opens executions for: the requested task first,
67
79
  * then its on-board descendants within the configured depth. Archived tasks
@@ -110,13 +122,20 @@ export declare function effectiveTaskPermission(task: TaskRecord): TaskRecord['p
110
122
  */
111
123
  /**
112
124
  * The immutable teammate name for one team-mode member. Teammate names are
113
- * lower-kebab-case and must stay unique for the lifetime of the Team, so the
114
- * run-group token is appended: two runs of the same tree never collide. A title
115
- * with no ASCII word characters (a CJK title, for example) keeps a stable
116
- * generic prefix and is still distinguished by the token.
125
+ * lower-kebab-case and must stay unique for the lifetime of the Team: the
126
+ * run-group token separates two runs of the same tree, and the member
127
+ * discriminator separates two members of one run that reduce to the same slug.
128
+ *
129
+ * The discriminator is not cosmetic. A title with no ASCII word characters (a
130
+ * CJK title, for example) carries no slug at all, so every such title of one
131
+ * run group collides on the generic prefix; Agent Teams answers every member
132
+ * after the first with `teammate name "..." was already used in this Team`,
133
+ * and those subtasks never start. Two ASCII titles that share a slug
134
+ * (`AI 强度预测` next to `AI 配比优化`) collide the same way.
117
135
  * @param title - the member task's title.
118
136
  * @param token - the run group id (the task id when no group is recorded).
137
+ * @param memberId - the member task's id, which makes the name unique in the run.
119
138
  * @returns the teammate name handed to the Agent Teams service.
120
139
  */
121
- export declare function teammateName(title: string, token: string): string;
140
+ export declare function teammateName(title: string, token: string, memberId?: string): string;
122
141
  export declare function resolveExecutionTargets(task: TaskRecord, tasks: readonly TaskRecord[], maxSubtaskDepth?: number): TaskRecord;