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

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 (120) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +59 -20
  3. package/README.zh.md +59 -20
  4. package/lib/client.js +6208 -624
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +4774 -493
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +31 -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/index.d.ts +23 -5
  26. package/lib/types/client/locales.d.ts +168 -1
  27. package/lib/types/client/native-panel.d.ts +6 -20
  28. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  29. package/lib/types/client/schedule-zone.d.ts +50 -0
  30. package/lib/types/client/seats.d.ts +18 -0
  31. package/lib/types/client/service.d.ts +32 -0
  32. package/lib/types/client/settings-form.d.ts +16 -0
  33. package/lib/types/core/controller.d.ts +46 -11
  34. package/lib/types/core/extension.d.ts +283 -0
  35. package/lib/types/core/handover.d.ts +13 -1
  36. package/lib/types/core/issue-form.d.ts +16 -0
  37. package/lib/types/core/poll-cadence.d.ts +31 -0
  38. package/lib/types/core/tasks.d.ts +228 -17
  39. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  40. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  41. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  42. package/lib/types/core/verification-budget.d.ts +52 -0
  43. package/lib/types/core/verification.d.ts +753 -0
  44. package/lib/types/core/workspace-target.d.ts +41 -0
  45. package/lib/types/host/agent-tools.d.ts +1 -1
  46. package/lib/types/host/extension-registry.d.ts +111 -0
  47. package/lib/types/host/llm-dispatch.d.ts +63 -0
  48. package/lib/types/host/verification-gate.d.ts +95 -0
  49. package/lib/types/host/verification-runner.d.ts +139 -0
  50. package/lib/types/host/workspace-evidence.d.ts +53 -0
  51. package/lib/types/host-ledger.d.ts +133 -14
  52. package/lib/types/host-runner.d.ts +55 -10
  53. package/lib/types/host-service.d.ts +205 -11
  54. package/lib/types/index.d.ts +134 -26
  55. package/lib/types/protocol.d.ts +92 -10
  56. package/lib/types/tool-surface.d.ts +71 -0
  57. package/package.json +28 -31
  58. package/src/client/PluginSettingsCard.tsx +9 -0
  59. package/src/client/TaskBoardSettingsCard.tsx +378 -66
  60. package/src/client/board/ClampedMarkdown.tsx +69 -0
  61. package/src/client/board/ConfirmDialog.tsx +7 -3
  62. package/src/client/board/EditTaskModal.tsx +5 -2
  63. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  64. package/src/client/board/NewTaskModal.tsx +168 -21
  65. package/src/client/board/TagManagerModal.tsx +235 -0
  66. package/src/client/board/TaskBoard.tsx +259 -103
  67. package/src/client/board/TaskCard.tsx +214 -81
  68. package/src/client/board/TaskDetail.tsx +289 -43
  69. package/src/client/board/TaskForm.tsx +13 -3
  70. package/src/client/board/VerificationReport.tsx +322 -0
  71. package/src/client/board/card-view.ts +220 -0
  72. package/src/client/board/icons.tsx +89 -0
  73. package/src/client/board/overlay.tsx +189 -0
  74. package/src/client/board/status-key.ts +30 -0
  75. package/src/client/board/task-markdown.module.css +137 -0
  76. package/src/client/board/task-markdown.tsx +106 -0
  77. package/src/client/board-settings.module.css +66 -0
  78. package/src/client/board.module.css +488 -63
  79. package/src/client/index.ts +82 -51
  80. package/src/client/locales.ts +343 -6
  81. package/src/client/native-panel.tsx +18 -2
  82. package/src/client/plugin-card-seat.ts +7 -0
  83. package/src/client/schedule-zone.ts +100 -2
  84. package/src/client/seats.tsx +58 -0
  85. package/src/client/service.ts +94 -0
  86. package/src/client/settings-card.module.css +31 -5
  87. package/src/client/settings-form.ts +27 -1
  88. package/src/core/controller.ts +105 -10
  89. package/src/core/extension.ts +376 -0
  90. package/src/core/freeze-snapshot.ts +2 -2
  91. package/src/core/handover.ts +16 -1
  92. package/src/core/issue-form.ts +19 -0
  93. package/src/core/poll-cadence.ts +41 -0
  94. package/src/core/store.ts +80 -15
  95. package/src/core/tasks.ts +293 -45
  96. package/src/core/use-cases/task-create.ts +30 -9
  97. package/src/core/use-cases/task-parent.ts +3 -2
  98. package/src/core/use-cases/task-schedule.ts +190 -25
  99. package/src/core/use-cases/task-tag.ts +110 -0
  100. package/src/core/use-cases/task-update.ts +13 -7
  101. package/src/core/verification-budget.ts +115 -0
  102. package/src/core/verification.ts +1594 -0
  103. package/src/core/workspace-target.ts +59 -0
  104. package/src/host/agent-tools.ts +125 -38
  105. package/src/host/extension-registry.ts +509 -0
  106. package/src/host/llm-dispatch.ts +87 -0
  107. package/src/host/verification-gate.ts +487 -0
  108. package/src/host/verification-runner.ts +702 -0
  109. package/src/host/workspace-evidence.ts +176 -0
  110. package/src/host-ai.ts +15 -9
  111. package/src/host-ledger.ts +468 -71
  112. package/src/host-routes.ts +15 -2
  113. package/src/host-runner.ts +106 -24
  114. package/src/host-service.ts +546 -40
  115. package/src/index.ts +305 -21
  116. package/src/protocol.ts +226 -28
  117. package/src/tool-surface.ts +95 -0
  118. package/lib/invariant.js +0 -6
  119. package/lib/types/invariant.d.ts +0 -3
  120. package/src/invariant.ts +0 -4
@@ -35,6 +35,14 @@ import { HttpTaskBoardHostTransport } from './host-api.ts'
35
35
  import { reportDailyHeartbeat } from './telemetry.ts'
36
36
  import { installPluginCard } from './plugin-card-seat.ts'
37
37
  import { createServedEntryForm } from './settings-entry-form.ts'
38
+ import { TaskBoardClientService } from './service.ts'
39
+ import {
40
+ TASK_BOARD_SERVICE_NAME,
41
+ type TaskBoardCardDecorationProps,
42
+ type TaskBoardDetailSectionProps,
43
+ type TaskBoardHostFace,
44
+ type TaskBoardSettingsSectionProps,
45
+ } from '../core/extension.ts'
38
46
 
39
47
  /** Locale namespace this plugin owns. */
40
48
  const NS = 'task-board'
@@ -95,6 +103,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
95
103
  * package can register without depending on the sibling UI package.
96
104
  */
97
105
  'web-ui.plugin.item': { kind: 'list'; scope: 'root'; owner: SettingsPluginItemOwnerProps }
106
+
107
+ /** Provider task-detail section; the board declares and renders it. */
108
+ 'task-board.detail.section': { kind: 'list'; scope: 'root'; owner: TaskBoardDetailSectionProps }
109
+ /** Provider settings section; the board declares and renders it. */
110
+ 'task-board.settings.section': { kind: 'list'; scope: 'root'; owner: TaskBoardSettingsSectionProps }
111
+ /** Provider card decoration; the board declares and renders it. */
112
+ 'task-board.card.decoration': { kind: 'list'; scope: 'root'; owner: TaskBoardCardDecorationProps }
98
113
  }
99
114
  }
100
115
 
@@ -118,15 +133,17 @@ declare module '@deepseek-ai/cordis' {
118
133
 
119
134
  /**
120
135
  * Required services (fiber inject waiting — the runtime must be up first).
121
- * The generated remote faces are probed at use time instead of injected:
122
- * `remote.agentPresets` only registers on 0.1.2-alpha.2 hosts (the
123
- * api-remotes contribution), so a hard wait would pend the entry forever
124
- * on hosts below that cohort, which serve the same roster through the
125
- * connection RPC face.
136
+ * The generated remote namespaces are not all hard injects: `remote.session`
137
+ * is one, while `remote.agentPresets` is reached through a scoped inject at
138
+ * use time (see mountUi), because the namespace mounts with the official
139
+ * api-remotes assembly applied beside this entry rather than before it.
126
140
  */
127
141
  export const inject = ['slots', 'sessions', 'workspaces', 'connection', 'configForms', 'locale', 'remote', 'remote.session', 'uiWorkspace', 'layout']
128
142
 
129
- /** One agent-preset row the mode picker consumes (either face's wire shape). */
143
+ /** The generated Remote namespace the mode picker reads its roster from. */
144
+ type PresetRemote = ClientRemote['agentPresets']
145
+
146
+ /** One agent-preset row the mode picker consumes. */
130
147
  interface PresetRosterRow {
131
148
  id: string
132
149
  name?: string
@@ -137,41 +154,17 @@ interface PresetRosterRow {
137
154
  }
138
155
 
139
156
  /**
140
- * Read the agent-preset roster through whichever face the running host
141
- * serves: the generated api-remotes face (`remote.agentPresets`,
142
- * 0.1.2-alpha.2) or the connection RPC face
143
- * (`connection.api.agentPresets`, hosts below that cohort). Answers
144
- * undefined when the host serves neither, so the caller leaves the picker
145
- * options untouched instead of erroring.
157
+ * Read the agent-preset roster through the generated api-remotes face. The
158
+ * caller passes the namespace resolved inside its scoped inject, so a host
159
+ * that serves no such namespace never reaches here; a host that serves it
160
+ * answers `ok: false` instead, which leaves the picker options untouched.
161
+ * @param remote - the `remote.agentPresets` namespace.
162
+ * @returns the roster, or a failed read the caller ignores.
146
163
  */
147
- async function readPresetRoster(
148
- ctx: ClientContext,
149
- remote: ClientRemote,
150
- ): Promise<{ ok: boolean; presets: readonly PresetRosterRow[] } | undefined> {
151
- // The cordis `remote` proxy throws on a property that was never injected
152
- // ("cannot get property X without inject") rather than returning undefined,
153
- // so the probe must guard the access — a hard read would abort mounting on
154
- // hosts below the 0.1.2-alpha.2 cohort instead of degrading to the legacy
155
- // connection RPC face below.
156
- let remotes: ClientRemote['agentPresets'] | undefined
157
- try {
158
- remotes = (remote as Partial<ClientRemote>).agentPresets
159
- } catch {
160
- remotes = undefined
161
- }
162
- if (remotes !== undefined) {
163
- const response = await remotes.list()
164
- if (!response.ok) return { ok: false, presets: [] }
165
- return { ok: true, presets: response.value.presets }
166
- }
167
- const connection = ctx.get('connection') as unknown as {
168
- api?: { agentPresets?: { list(request: Record<string, never>): Promise<{ result: { ok: boolean; value?: { presets?: readonly PresetRosterRow[] } } }> } }
169
- }
170
- const legacy = connection.api?.agentPresets
171
- if (legacy === undefined) return undefined
172
- const response = await legacy.list({})
173
- if (!response.result.ok || response.result.value === undefined) return { ok: false, presets: [] }
174
- return { ok: true, presets: response.result.value.presets ?? [] }
164
+ async function readPresetRoster(remote: PresetRemote): Promise<{ ok: boolean; presets: readonly PresetRosterRow[] }> {
165
+ const response = await remote.list()
166
+ if (!response.ok) return { ok: false, presets: [] }
167
+ return { ok: true, presets: response.value.presets }
175
168
  }
176
169
 
177
170
  /**
@@ -211,12 +204,26 @@ export function apply(ctx: ClientContext): void {
211
204
  // namespace, contributed to whichever plugin-card seat this host declares
212
205
  // (the family group's list seat, or the official bundle-configuration seat).
213
206
  const settingsForm = bindSettingsForm(ctx)
214
- const settingsCard = new TaskBoardSettingsCardController(settingsForm)
207
+ // The board's browser-half extension service. It is provided before any
208
+ // controller exists (the UI mounts only once the settings form settles), so
209
+ // providers can subscribe and register visibility immediately.
210
+ const clientService = new TaskBoardClientService()
211
+ // A capture-only test context implements no service registry; the board's
212
+ // own surfaces still mount.
213
+ if (typeof (ctx as { provide?: unknown }).provide === 'function') {
214
+ ;(ctx.provide as unknown as (name: string, value: unknown) => void)(TASK_BOARD_SERVICE_NAME, clientService as unknown as TaskBoardHostFace)
215
+ }
216
+ ctx.effect(() => () => { clientService.detach() }, 'task-board: extension service')
217
+
218
+ const settingsCard = new TaskBoardSettingsCardController(settingsForm, request => clientService.dispatch(request))
215
219
  installPluginCard(ctx, {
216
220
  bundle: '@linxin666/dsh-client-ui-task-board',
217
221
  id: 'task-board',
218
222
  order: 110,
219
223
  locale: NS,
224
+ // The board's own settings card declares the provider settings seat; the
225
+ // provider registers into it and this card renders it.
226
+ children: { 'task-board.settings.section': { kind: 'list', scope: 'root' } },
220
227
  inject: () => settingsCard.inject(),
221
228
  component: TaskBoardSettingsCard,
222
229
  })
@@ -263,6 +270,9 @@ export function apply(ctx: ClientContext): void {
263
270
  },
264
271
  })
265
272
  controller.start()
273
+ // The extension service follows the live controller: providers observe the
274
+ // board mirror and dispatch through it while the board runs.
275
+ clientService.attach(controller)
266
276
 
267
277
  const disposers: Array<() => void> = []
268
278
 
@@ -289,10 +299,10 @@ export function apply(ctx: ClientContext): void {
289
299
  const created = await workspaces.create({ path })
290
300
  return { workspaceId: created.workspaceId }
291
301
  })
292
- const pushPresetOptions = async (): Promise<void> => {
302
+ const pushPresetOptions = async (presetRemote: PresetRemote): Promise<void> => {
293
303
  try {
294
- const roster = await readPresetRoster(ctx, remote)
295
- if (roster === undefined || !roster.ok) return
304
+ const roster = await readPresetRoster(presetRemote)
305
+ if (!roster.ok) return
296
306
  controller.setExecutionOptions({
297
307
  presets: roster.presets.map(preset => ({
298
308
  id: preset.id,
@@ -308,6 +318,25 @@ export function apply(ctx: ClientContext): void {
308
318
  console.error('[dsh-task-board] agent preset roster read failed', error)
309
319
  }
310
320
  }
321
+ // The roster namespace mounts with the official api-remotes assembly,
322
+ // which the page applies beside this entry: at mount time the namespace is
323
+ // usually still absent, and reading it through `remote.agentPresets`
324
+ // before it lands throws ("cannot get property ... without inject"), so a
325
+ // one-shot probe leaves the mode picker with nothing but "inherit" for the
326
+ // page lifetime. A hard inject is no better — it would pend the whole
327
+ // board on a deployment that never mounts the namespace. The scoped inject
328
+ // runs the feed exactly when the namespace exists and re-runs it if the
329
+ // namespace is replaced; the reconnect read inside covers a deployment
330
+ // change behind the same namespace.
331
+ const presetRosterFeed = ctx.inject(['remote.agentPresets'], (inner) => {
332
+ const presetRemote = inner.get('remote.agentPresets') as PresetRemote | undefined
333
+ if (presetRemote === undefined) return
334
+ void pushPresetOptions(presetRemote)
335
+ // Subscribed inside the injected scope, so a namespace that goes away
336
+ // takes its listener with it instead of leaving one per remount.
337
+ return inner.on('connection/reset', () => { void pushPresetOptions(presetRemote) })
338
+ })
339
+ disposers.push(() => { void presetRosterFeed.dispose() })
311
340
  const pushModelOptions = async (): Promise<void> => {
312
341
  try {
313
342
  let models: Array<{ id: string; name?: string; provider?: string }> = []
@@ -372,12 +401,8 @@ export function apply(ctx: ClientContext): void {
372
401
  console.error('[dsh-task-board] model options read failed', error)
373
402
  }
374
403
  }
375
- void pushPresetOptions()
376
404
  void pushModelOptions()
377
- disposers.push(ctx.on('connection/reset', () => {
378
- void pushPresetOptions()
379
- void pushModelOptions()
380
- }))
405
+ disposers.push(ctx.on('connection/reset', () => { void pushModelOptions() }))
381
406
  // Native panel surfaces: one sidebar row and one center-column page, both
382
407
  // through the official slot seats the shell itself renders. The layout's
383
408
  // panel selection is reconciled back into the controller so the board's own
@@ -400,6 +425,7 @@ export function apply(ctx: ClientContext): void {
400
425
 
401
426
  uiDisposer = () => {
402
427
  for (const dispose of disposers.splice(0)) dispose()
428
+ clientService.detach()
403
429
  controller.dispose()
404
430
  uiDisposer = undefined
405
431
  }
@@ -409,8 +435,13 @@ export function apply(ctx: ClientContext): void {
409
435
  const enabled = snapshot.status === 'ready'
410
436
  ? snapshot.value?.enabled ?? true
411
437
  : snapshot.status === 'unavailable'
412
- if (enabled) mountUi()
413
- else uiDisposer?.()
438
+ if (enabled) {
439
+ mountUi()
440
+ clientService.setEnabled(true)
441
+ } else {
442
+ clientService.setEnabled(false)
443
+ uiDisposer?.()
444
+ }
414
445
  }
415
446
  const unsubscribeSettings = settingsForm.subscribe(syncEnabled)
416
447
  // The mounted surfaces and the settings subscription belong to this fiber.