@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
@@ -33,8 +33,16 @@ import { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardS
33
33
  import { en, zh, setRuntimeTranslate, type TaskBoardKey } from './locales.ts'
34
34
  import { HttpTaskBoardHostTransport } from './host-api.ts'
35
35
  import { reportDailyHeartbeat } from './telemetry.ts'
36
- import { installPluginCard } from './plugin-card-seat.ts'
36
+ import { installBoardCard } from './board-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
 
@@ -211,12 +226,26 @@ export function apply(ctx: ClientContext): void {
211
226
  // namespace, contributed to whichever plugin-card seat this host declares
212
227
  // (the family group's list seat, or the official bundle-configuration seat).
213
228
  const settingsForm = bindSettingsForm(ctx)
214
- const settingsCard = new TaskBoardSettingsCardController(settingsForm)
215
- installPluginCard(ctx, {
229
+ // The board's browser-half extension service. It is provided before any
230
+ // controller exists (the UI mounts only once the settings form settles), so
231
+ // providers can subscribe and register visibility immediately.
232
+ const clientService = new TaskBoardClientService()
233
+ // A capture-only test context implements no service registry; the board's
234
+ // own surfaces still mount.
235
+ if (typeof (ctx as { provide?: unknown }).provide === 'function') {
236
+ ;(ctx.provide as unknown as (name: string, value: unknown) => void)(TASK_BOARD_SERVICE_NAME, clientService as unknown as TaskBoardHostFace)
237
+ }
238
+ ctx.effect(() => () => { clientService.detach() }, 'task-board: extension service')
239
+
240
+ const settingsCard = new TaskBoardSettingsCardController(settingsForm, request => clientService.dispatch(request))
241
+ installBoardCard(ctx, {
216
242
  bundle: '@linxin666/dsh-client-ui-task-board',
217
243
  id: 'task-board',
218
244
  order: 110,
219
245
  locale: NS,
246
+ // The board's own settings card declares the provider settings seat; the
247
+ // provider registers into it and this card renders it.
248
+ children: { 'task-board.settings.section': { kind: 'list', scope: 'root' } },
220
249
  inject: () => settingsCard.inject(),
221
250
  component: TaskBoardSettingsCard,
222
251
  })
@@ -263,6 +292,9 @@ export function apply(ctx: ClientContext): void {
263
292
  },
264
293
  })
265
294
  controller.start()
295
+ // The extension service follows the live controller: providers observe the
296
+ // board mirror and dispatch through it while the board runs.
297
+ clientService.attach(controller)
266
298
 
267
299
  const disposers: Array<() => void> = []
268
300
 
@@ -400,6 +432,7 @@ export function apply(ctx: ClientContext): void {
400
432
 
401
433
  uiDisposer = () => {
402
434
  for (const dispose of disposers.splice(0)) dispose()
435
+ clientService.detach()
403
436
  controller.dispose()
404
437
  uiDisposer = undefined
405
438
  }
@@ -409,8 +442,13 @@ export function apply(ctx: ClientContext): void {
409
442
  const enabled = snapshot.status === 'ready'
410
443
  ? snapshot.value?.enabled ?? true
411
444
  : snapshot.status === 'unavailable'
412
- if (enabled) mountUi()
413
- else uiDisposer?.()
445
+ if (enabled) {
446
+ mountUi()
447
+ clientService.setEnabled(true)
448
+ } else {
449
+ clientService.setEnabled(false)
450
+ uiDisposer?.()
451
+ }
414
452
  }
415
453
  const unsubscribeSettings = settingsForm.subscribe(syncEnabled)
416
454
  // The mounted surfaces and the settings subscription belong to this fiber.
@@ -26,7 +26,6 @@ export const zh = {
26
26
  'board.status.failed': '已失败',
27
27
  'board.runs': '次执行',
28
28
  'board.pending': '正在提交',
29
- 'board.updated': '更新于',
30
29
  'board.created': '创建于',
31
30
  'board.hostError': 'Host 操作失败:{error}',
32
31
  'board.hostError.notMounted': '任务看板的后台接口没有挂载:宿主没有加载这个插件(可能未安装、未启用,或该行启动失败)。重启 DSH 服务后再试',
@@ -108,6 +107,22 @@ export const zh = {
108
107
  'board.tagFilter': '按标签筛选',
109
108
  'board.tagFilterClear': '清除标签筛选',
110
109
  'board.tagEmpty': '没有同时带这些标签的任务',
110
+ 'board.tagManage': '管理标签',
111
+ 'tags.title': '标签管理',
112
+ 'tags.hint': '标签是顶栏筛选与卡片徽章的来源。重命名会写到所有卡片上(与已有同名标签合并),删除会从全部任务(含已归档)上移除;任务本身不受影响。',
113
+ 'tags.empty': '当前没有任何标签在使用。',
114
+ 'tags.usage': '{count} 个任务',
115
+ 'tags.rename': '重命名',
116
+ 'tags.renameLabel': '重命名标签 {name}',
117
+ 'tags.delete': '删除',
118
+ 'tags.deleteTitle': '删除标签',
119
+ 'tags.deleteConfirm': '从 {count} 个任务上移除标签「{name}」?该标签的执行提示会一并移除,任务本身不受影响。',
120
+ 'tags.deleteOk': '删除',
121
+ 'tags.save': '保存',
122
+ 'tags.mergeHint': '将合并进已有标签「{name}」',
123
+ 'tags.renameFailed': '重命名未被接受。',
124
+ 'tags.deleteFailed': '删除未被接受。',
125
+ 'tags.done': '完成',
111
126
  'detail.permissionPending': '权限待确认:本卡有效权限({permission})高于会话默认,手动执行与定时调度都会被拒绝,需人工确认后才能运行。',
112
127
  'detail.permissionConfirm': '确认权限绑定',
113
128
  'detail.permissionConfirmed': '已确认权限绑定 · {time}',
@@ -123,6 +138,8 @@ export const zh = {
123
138
  'new.archiveOriginal': '创建后归档原任务',
124
139
  'detail.prompt': '执行 Prompt',
125
140
  'detail.description': '描述',
141
+ 'detail.expand': '展开全文',
142
+ 'detail.collapse': '收起',
126
143
  'detail.execution': '执行记录',
127
144
  'detail.noExecution': '尚未执行',
128
145
  'detail.run': '执行',
@@ -146,6 +163,9 @@ export const zh = {
146
163
  'delete.cancel': '取消',
147
164
  'status.move.backlog': '移到待规划',
148
165
  'status.move.todo': '移到待办',
166
+ 'status.move.running': '移到进行中',
167
+ 'status.move.done': '移到已完成',
168
+ 'status.move.failed': '移到已失败',
149
169
  'exec.error.noWorkspace': '没有可用工作区,无法执行任务',
150
170
  'exec.error.promptRejected': 'Prompt 被拒绝',
151
171
  'run.failed': '执行失败:{error}',
@@ -205,12 +225,15 @@ export const zh = {
205
225
  'exec.reuseSessionHint': '开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。',
206
226
  'exec.goalRun': '以 dsh 内置的 /goal 开始执行任务',
207
227
  'exec.goalRunHint': '默认开启:执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。关闭则只执行一轮普通对话。',
228
+ 'exec.skipVerification': '跳过本任务的验收',
229
+ 'exec.skipVerificationHint': '默认关闭:勾选后本任务以 goal 形式执行时不再拦截 update_goal 标记完成,也不要求通过验收;执行记录会标注「该卡片跳过验收」。仅对之后新开的执行生效,验收总开关关闭时本项无意义。',
208
230
  'detail.executionSettings': '执行设置',
209
231
  'exec.hint': '执行时生效:工作区决定执行会话落在哪个工作区;Agent 预设决定会话的组合(工具面与提示词段);权限经 /permission 命令应用到会话。留空则继承部署默认。',
210
232
  // 插件设置卡片(settings.plugin.item 席位)。
211
233
  'settings.title': '任务看板',
212
- 'settings.description': '控制 Host 任务看板、agent 播报与运行期间的系统空闲睡眠保护。',
234
+ 'settings.description': '任务编排、agent 播报与目标验收。',
213
235
  'settings.enabled': '启用任务看板',
236
+ 'settings.enabledCardHint': '总开关、播报、睡眠保护与子任务层级。',
214
237
  'settings.enabledHint': '关闭后隐藏侧边栏入口与看板视图,并注销 task_board_* agent 工具;开启时任何会话都可以通过这些工具读写看板、子任务与定时计划。',
215
238
  'settings.announceToAgent': '向 agent 播报任务看板',
216
239
  'settings.announceToAgentHint': '开启:每条 agent 系统提示都会包含本看板的说明;关闭:不播报,agent 仅在用户主动提及时了解看板。',
@@ -238,6 +261,65 @@ export const zh = {
238
261
  'settings.maxSubtaskDepth': '子任务深度上限',
239
262
  'settings.maxSubtaskDepthHint': '默认 1:一个任务只允许一层子任务,子任务不能再创建或关联子任务。最大 3。执行父任务会并发执行它的整棵子任务树,层级越深,一次执行开启的会话越多。',
240
263
  'settings.maxSubtaskDepthOption': '{depth} 层',
264
+ 'settings.goalVerificationTitle': '任务验收',
265
+ 'settings.goalVerification': '启用任务验收',
266
+ 'settings.goalVerificationCardHint': '验收开关、裁判模型与判据阈值。',
267
+ 'settings.goalVerificationHint': '默认打开:以 goal 形式执行的任务在 update_goal 标记完成前必须先通过一次验收(三项 coding 判据(本分区面向工程任务)、阈值 0.65、每项两轮且交换 A/B 位置,实际工作须胜过空工作基线)。首次不通过会把总分、逐项分数与可定位问题反馈给 agent 修复,同一执行最多两次验收;第二次仍不通过则本次执行判失败。仅作用于本插件以 goal 形式执行的任务,不影响普通聊天,也不影响显式 goalRun:false 的单回合任务。每次验收都会真实调用裁判模型并消耗额度。',
268
+ 'settings.goalVerificationModel': '验收模型',
269
+ 'settings.goalVerificationModelHint': '留空即继承宿主:使用宿主模型目录的默认路由,而不是任务卡钉住的执行模型。',
270
+ 'settings.goalVerificationModelInherit': '继承宿主({model})',
271
+ 'settings.goalVerificationModelInheritUnknown': '继承宿主(宿主未提供模型目录)',
272
+ 'settings.goalVerificationEffort': '推理强度',
273
+ 'settings.goalVerificationEffortHint': '留空即继承宿主默认档位。目标模型不支持所选档位时不会盲传:验收改用该模型自己的默认档位,并在下方标注回退。',
274
+ 'settings.goalVerificationEffortInherit': '继承宿主({effort})',
275
+ 'settings.goalVerificationEffortInheritUnknown': '继承宿主(模型默认档位)',
276
+ 'settings.goalVerificationResolved': '实际解析后的验收配置',
277
+ 'settings.goalVerificationResolvedModel': '裁判模型:{model}',
278
+ 'settings.goalVerificationResolvedEffort': '推理强度:{effort}',
279
+ 'settings.goalVerificationResolvedNoEffort': '推理强度:不显式传参(用模型自身默认)',
280
+ 'settings.goalVerificationResolvedSource': '来源:{source}',
281
+ 'settings.goalVerificationSourceInherit': '继承宿主',
282
+ 'settings.goalVerificationSourceExplicit': '显式配置',
283
+ 'settings.goalVerificationResolvedPreset': '判据:coding(Specification Adherence / Output Match / Error Signal Detection),阈值 {threshold}',
284
+ 'settings.goalVerificationRouteMissing': '宿主未提供模型目录,无法解析验收模型:开启后 goal 任务的完成声明会被拒绝并记录为验收异常。',
285
+ 'settings.goalVerificationEffortFallback': '所选推理强度 {requested} 不被目标模型支持,已回退为 {resolved}。',
286
+ 'settings.goalVerificationEffortFallbackNone': '所选推理强度 {requested} 不被目标模型支持,已回退为该模型默认档位(不显式传参)。',
287
+ 'settings.goalVerificationModelInvalid': '配置的验收模型不是 provider/model 形式,已回退宿主默认。',
288
+ 'running.executing': '执行中',
289
+ 'running.verifying': '验收中',
290
+ 'running.repairing': '验收未通过修复中',
291
+ 'running.verificationPassed': '验收通过',
292
+ 'running.verificationFailed': '验收未通过',
293
+ 'verify.title': '验收报告',
294
+ 'verify.status.passed': '通过',
295
+ 'verify.status.failed': '未通过',
296
+ 'verify.status.exception': '异常',
297
+ 'verify.status.verifying': '验收中',
298
+ 'verify.status.pending': '待验收',
299
+ 'verify.status.off': '本次执行未启用验收',
300
+ 'verify.summary': '总分 {score}(阈值 {threshold})· 空工作基线 {baseline}',
301
+ 'verify.criteria': '逐项判据',
302
+ 'verify.criterion': '{name}:{score}(阈值 {threshold})',
303
+ 'verify.rounds': '每项 {rounds} 轮,交换 A/B 位置后取平均',
304
+ 'verify.judge': '裁判模型 {model}(推理强度 {effort})',
305
+ 'verify.judgeNoEffort': '裁判模型 {model}(推理强度:不显式传参)',
306
+ 'verify.judgeInherited': '裁判模型 {model}(继承宿主)',
307
+ 'verify.attempt': '第 {index} 次验收',
308
+ 'verify.attemptException': '第 {index} 次验收异常',
309
+ 'verify.counts': '质量验收 {quality}/{max} · 验收异常 {exceptions}/{max}',
310
+ 'verify.findings': '问题反馈',
311
+ 'verify.noFindings': '本次裁判没有报告可定位的问题',
312
+ 'verify.evidence': '证据范围:{chars} 字符 · {entries} 条轨迹 · 截断 {omitted} 字符',
313
+ 'verify.workspaceEvidence': '其中 {files} 个文件变更取自宿主自身观察',
314
+ 'verify.usage': '用量:请求 {calls} 次 · 输入 {input} · 输出 {output} · 推理 {reasoning} tokens',
315
+ 'verify.usageIncomplete': '(至少一次请求的用量未知,以上为下限)',
316
+ 'verify.finalFailure': '判定依据:{reason}',
317
+ 'verify.applicability.disabled': '本次执行启动时验收开关关闭,按原有回合判定结算。',
318
+ 'verify.applicability.goalUnavailable': '本次执行未成为 goal 执行(/goal 被拒绝或不可用),验收未强制执行。',
319
+ 'verify.applicability.skipped': '该任务卡勾选了「跳过验收」,本次执行未经验收判定。',
320
+ 'verify.applicability.teamMember': '团队执行成员:由 Lead 的团队汇总证据统一验收。',
321
+ 'verify.effortFallback': '推理强度回退:{requested} → {resolved}',
322
+ 'verify.thresholdValue': '{value}%',
241
323
  // 子任务(父子关系、级联执行、关联/解除关联)。
242
324
  'detail.parent': '父任务',
243
325
  'detail.parent.open': '打开父任务',
@@ -262,6 +344,25 @@ export const zh = {
262
344
  'card.subtasksFailed': '{count} 失败',
263
345
  'card.subtasksRunning': '{count} 运行中',
264
346
  'card.subtasksBreakdown': '子任务 {total}:已完成 {done},运行中 {running},失败 {failed}',
347
+ 'card.result.succeeded': '最近成功',
348
+ 'card.result.failed': '最近失败',
349
+ 'card.result.cancelled': '最近取消',
350
+ 'card.declared': '手动结算',
351
+ 'card.declaredHint': '卡片所在列与最近一次执行结果不一致:该列由人工移动或外部系统(如 Issue 关闭)设置,而不是这次执行的结算。',
352
+ 'card.openSession': '打开最近执行的会话',
353
+ 'card.tagsLabel': '标签 {tags}',
354
+ 'card.time.created': '创建于 {time}',
355
+ 'card.time.started': '已运行 {time}',
356
+ 'card.time.settled': '结算于 {time}',
357
+ 'card.time.archived': '归档于 {time}',
358
+ 'card.time.updated': '更新于 {time}',
359
+ 'board.density.compact': '切换为紧凑卡片',
360
+ 'board.density.comfortable': '切换为完整卡片',
361
+ 'board.group.today': '今天',
362
+ 'board.group.week': '近 7 天',
363
+ 'board.group.earlier': '更早',
364
+ 'board.group.expand': '展开 {count} 张',
365
+ 'board.group.collapse': '收起',
265
366
  'board.hideSubtasks': '隐藏子任务',
266
367
  'board.showSubtasks': '显示子任务',
267
368
  'board.subtaskFilterHint': '看板默认只显示父任务;搜索或按标签筛选时会自动展开子任务。',
@@ -288,7 +389,6 @@ export const en: Record<keyof typeof zh, string> = {
288
389
  'board.status.failed': 'Failed',
289
390
  'board.runs': 'runs',
290
391
  'board.pending': 'Submitting',
291
- 'board.updated': 'Updated',
292
392
  'board.created': 'Created',
293
393
  'board.hostError': 'Host action failed: {error}',
294
394
  'board.hostError.notMounted': 'The task board Host API is not mounted: the Host did not load this plugin (not installed, disabled, or its row failed to start). Restart the DSH service and try again',
@@ -370,6 +470,22 @@ export const en: Record<keyof typeof zh, string> = {
370
470
  'board.tagFilter': 'Filter by tag',
371
471
  'board.tagFilterClear': 'Clear tag filter',
372
472
  'board.tagEmpty': 'No task carries all of these tags',
473
+ 'board.tagManage': 'Manage labels',
474
+ 'tags.title': 'Label manager',
475
+ 'tags.hint': 'Labels feed the board filter and the card badges. Renaming writes to every card (merging into a label that already exists), and deleting removes the label from every task including archived ones; the tasks themselves are untouched.',
476
+ 'tags.empty': 'No labels are in use.',
477
+ 'tags.usage': '{count} tasks',
478
+ 'tags.rename': 'Rename',
479
+ 'tags.renameLabel': 'Rename the label {name}',
480
+ 'tags.delete': 'Delete',
481
+ 'tags.deleteTitle': 'Delete label',
482
+ 'tags.deleteConfirm': 'Remove the label "{name}" from {count} task(s)? Its execution hint is removed with it; the tasks themselves are unaffected.',
483
+ 'tags.deleteOk': 'Delete',
484
+ 'tags.save': 'Save',
485
+ 'tags.mergeHint': 'Merges into the existing label "{name}"',
486
+ 'tags.renameFailed': 'The rename was not accepted.',
487
+ 'tags.deleteFailed': 'The deletion was not accepted.',
488
+ 'tags.done': 'Done',
373
489
  'detail.permissionPending': 'Permission pending confirmation: this card\'s effective permission ({permission}) is above the session default; manual runs and cron are refused until a human confirms.',
374
490
  'detail.permissionConfirm': 'Confirm permission binding',
375
491
  'detail.permissionConfirmed': 'Permission confirmed · {time}',
@@ -385,6 +501,8 @@ export const en: Record<keyof typeof zh, string> = {
385
501
  'new.archiveOriginal': 'Archive original task upon creation',
386
502
  'detail.prompt': 'Run Prompt',
387
503
  'detail.description': 'Description',
504
+ 'detail.expand': 'Show more',
505
+ 'detail.collapse': 'Show less',
388
506
  'detail.execution': 'Execution History',
389
507
  'detail.noExecution': 'Not executed yet',
390
508
  'detail.run': 'Run',
@@ -408,6 +526,9 @@ export const en: Record<keyof typeof zh, string> = {
408
526
  'delete.cancel': 'Cancel',
409
527
  'status.move.backlog': 'Move to Backlog',
410
528
  'status.move.todo': 'Move to To Do',
529
+ 'status.move.running': 'Move to Running',
530
+ 'status.move.done': 'Move to Done',
531
+ 'status.move.failed': 'Move to Failed',
411
532
  'exec.error.noWorkspace': 'No workspace is available to run the task',
412
533
  'exec.error.promptRejected': 'Prompt rejected',
413
534
  'run.failed': 'Run failed: {error}',
@@ -467,12 +588,15 @@ export const en: Record<keyof typeof zh, string> = {
467
588
  'exec.reuseSessionHint': 'When on, later runs continue in the previous session (when that session is idle and still exists) instead of starting a new conversation each time; the pinned permission and model above are re-applied on every reuse.',
468
589
  'exec.goalRun': "Start the run with dsh's built-in /goal",
469
590
  'exec.goalRunHint': 'On by default: the run arms dsh built-in /goal with the task objective, so the session keeps working automatic continuation rounds until the agent marks the goal complete, and the board settles the execution only when that goal really ends. Off: one plain turn.',
591
+ 'exec.skipVerification': 'Skip acceptance for this task',
592
+ 'exec.skipVerificationHint': 'Off by default: when checked, a goal-form run of this task no longer gates update_goal on a passing acceptance, and the execution record is marked as skipped. It applies only to executions started afterwards, and means nothing while the board-wide acceptance switch is off.',
470
593
  'detail.executionSettings': 'Execution Settings',
471
594
  'exec.hint': 'Applied when the task runs: the workspace decides where the execution session lands; the agent preset composes the session (tool face and prompt sections); the permission is applied through the /permission command. Blank inherits the deployment default.',
472
595
  // Plugin settings card (the `settings.plugin.item` seat).
473
596
  'settings.title': 'Task Board',
474
- 'settings.description': 'Configure the Host task board, agent announcement, and idle-system-sleep protection while work is pending.',
597
+ 'settings.description': 'Task orchestration, agent announcement and goal acceptance.',
475
598
  'settings.enabled': 'Enable the task board',
599
+ 'settings.enabledCardHint': 'Master switch, announcement, sleep protection and subtask depth.',
476
600
  'settings.enabledHint': 'When off, the sidebar entry and board view are hidden and the task_board_* agent tools are unregistered; while on, any session can read and drive the board, its subtasks and its schedules through those tools.',
477
601
  'settings.announceToAgent': 'Announce the task board to agents',
478
602
  'settings.announceToAgentHint': 'On: every agent system prompt includes a note about this board. Off: no announcement; agents learn about the board only when you mention it.',
@@ -500,6 +624,65 @@ export const en: Record<keyof typeof zh, string> = {
500
624
  'settings.maxSubtaskDepth': 'Subtask depth limit',
501
625
  'settings.maxSubtaskDepthHint': 'Default 1: a task may carry one level of subtasks, and a subtask cannot create or link further subtasks. Maximum 3. Running a task also runs its whole subtask tree concurrently, and every extra level opens more sessions per run.',
502
626
  'settings.maxSubtaskDepthOption': '{depth} levels',
627
+ 'settings.goalVerificationTitle': 'Task acceptance',
628
+ 'settings.goalVerification': 'Enable task acceptance',
629
+ 'settings.goalVerificationCardHint': 'The switch, the judge model and the criteria threshold.',
630
+ 'settings.goalVerificationHint': 'On by default: a task executed in goal form must pass one acceptance before update_goal may mark it complete (the three coding criteria, aimed at engineering tasks, a 0.65 threshold, two rounds per criterion with the A/B slots swapped, and the work must beat the empty-work baseline). A first failure returns the total, the per-criterion scores and the located findings to the fixing agent, and one execution may accept at most twice; a second failure fails that execution. It affects only this board goal-form executions, never plain chat and never a task pinned to goalRun: false. Every acceptance really calls the judge model and spends quota.',
631
+ 'settings.goalVerificationModel': 'Judge model',
632
+ 'settings.goalVerificationModelHint': 'Blank inherits the host: the default route of the host model catalog, never the card pinned execution model.',
633
+ 'settings.goalVerificationModelInherit': 'Inherit host ({model})',
634
+ 'settings.goalVerificationModelInheritUnknown': 'Inherit host (no model catalog)',
635
+ 'settings.goalVerificationEffort': 'Reasoning effort',
636
+ 'settings.goalVerificationEffortHint': 'Blank inherits the host default level. A level the target model does not declare is never sent blindly: the acceptance falls back to that model own default level, and the fallback is reported below.',
637
+ 'settings.goalVerificationEffortInherit': 'Inherit host ({effort})',
638
+ 'settings.goalVerificationEffortInheritUnknown': 'Inherit host (model default level)',
639
+ 'settings.goalVerificationResolved': 'Resolved acceptance configuration',
640
+ 'settings.goalVerificationResolvedModel': 'Judge model: {model}',
641
+ 'settings.goalVerificationResolvedEffort': 'Reasoning effort: {effort}',
642
+ 'settings.goalVerificationResolvedNoEffort': 'Reasoning effort: not sent explicitly (the model default)',
643
+ 'settings.goalVerificationResolvedSource': 'Source: {source}',
644
+ 'settings.goalVerificationSourceInherit': 'inherited from the host',
645
+ 'settings.goalVerificationSourceExplicit': 'explicit configuration',
646
+ 'settings.goalVerificationResolvedPreset': 'Rubric: coding (Specification Adherence / Output Match / Error Signal Detection), threshold {threshold}',
647
+ 'settings.goalVerificationRouteMissing': 'The host serves no model catalog, so no judge route resolves: with acceptance on, a goal completion claim is refused and recorded as an acceptance anomaly.',
648
+ 'settings.goalVerificationEffortFallback': 'The selected reasoning effort {requested} is not supported by the target model; it falls back to {resolved}.',
649
+ 'settings.goalVerificationEffortFallbackNone': 'The selected reasoning effort {requested} is not supported by the target model; it falls back to that model default level (not sent explicitly).',
650
+ 'settings.goalVerificationModelInvalid': 'The configured judge model is not a provider/model route; the host default is used instead.',
651
+ 'running.executing': 'Executing',
652
+ 'running.verifying': 'Verifying',
653
+ 'running.repairing': 'Fixing a failed acceptance',
654
+ 'running.verificationPassed': 'Acceptance passed',
655
+ 'running.verificationFailed': 'Acceptance failed',
656
+ 'verify.title': 'Acceptance report',
657
+ 'verify.status.passed': 'Passed',
658
+ 'verify.status.failed': 'Failed',
659
+ 'verify.status.exception': 'Anomaly',
660
+ 'verify.status.verifying': 'Verifying',
661
+ 'verify.status.pending': 'Not yet verified',
662
+ 'verify.status.off': 'Acceptance was off for this execution',
663
+ 'verify.summary': 'Total {score} (threshold {threshold}) · empty-work baseline {baseline}',
664
+ 'verify.criteria': 'Per-criterion scores',
665
+ 'verify.criterion': '{name}: {score} (threshold {threshold})',
666
+ 'verify.rounds': '{rounds} rounds per criterion, A/B slots swapped, averaged',
667
+ 'verify.judge': 'Judge {model} (reasoning effort {effort})',
668
+ 'verify.judgeNoEffort': 'Judge {model} (reasoning effort: not sent explicitly)',
669
+ 'verify.judgeInherited': 'Judge {model} (inherited from the host)',
670
+ 'verify.attempt': 'Acceptance {index}',
671
+ 'verify.attemptException': 'Acceptance anomaly {index}',
672
+ 'verify.counts': 'Quality verdicts {quality}/{max} · anomalies {exceptions}/{max}',
673
+ 'verify.findings': 'Findings',
674
+ 'verify.noFindings': 'The judge reported no locatable finding',
675
+ 'verify.evidence': 'Evidence: {chars} characters · {entries} trace entries · {omitted} characters truncated',
676
+ 'verify.workspaceEvidence': '{files} changed file(s) observed by the host',
677
+ 'verify.usage': 'Usage: {calls} requests · {input} input · {output} output · {reasoning} reasoning tokens',
678
+ 'verify.usageIncomplete': ' (at least one request usage is unknown; these counts are a floor)',
679
+ 'verify.finalFailure': 'Verdict basis: {reason}',
680
+ 'verify.applicability.disabled': 'Acceptance was off when this execution started; it settles on the historical verdict.',
681
+ 'verify.applicability.goalUnavailable': 'This execution never became a goal run (/goal was refused or unavailable), so acceptance was not enforced.',
682
+ 'verify.applicability.skipped': 'This card checked Skip acceptance, so the execution was never judged.',
683
+ 'verify.applicability.teamMember': 'Team member: the Lead team-summary evidence is accepted as one execution.',
684
+ 'verify.effortFallback': 'Reasoning effort fallback: {requested} → {resolved}',
685
+ 'verify.thresholdValue': '{value}%',
503
686
  // Subtasks (parent/child links, cascade runs, link/detach).
504
687
  'detail.parent': 'Parent task',
505
688
  'detail.parent.open': 'Open parent task',
@@ -524,11 +707,32 @@ export const en: Record<keyof typeof zh, string> = {
524
707
  'card.subtasksFailed': '{count} failed',
525
708
  'card.subtasksRunning': '{count} running',
526
709
  'card.subtasksBreakdown': 'Subtasks {total}: {done} done, {running} running, {failed} failed',
710
+ 'card.result.succeeded': 'last succeeded',
711
+ 'card.result.failed': 'last failed',
712
+ 'card.result.cancelled': 'last cancelled',
713
+ 'card.declared': 'set by hand',
714
+ 'card.declaredHint': 'The column disagrees with the latest run: it was set by a manual move or an external system (such as a closed issue), not by that run settling.',
715
+ 'card.openSession': 'Open the latest execution session',
716
+ 'card.tagsLabel': 'labels {tags}',
717
+ 'card.time.created': 'Created {time}',
718
+ 'card.time.started': 'Running {time}',
719
+ 'card.time.settled': 'Settled {time}',
720
+ 'card.time.archived': 'Archived {time}',
721
+ 'card.time.updated': 'Updated {time}',
722
+ 'board.density.compact': 'Switch to compact cards',
723
+ 'board.density.comfortable': 'Switch to full cards',
724
+ 'board.group.today': 'Today',
725
+ 'board.group.week': 'Last 7 days',
726
+ 'board.group.earlier': 'Earlier',
727
+ 'board.group.expand': 'Show {count}',
728
+ 'board.group.collapse': 'Collapse',
527
729
  'board.hideSubtasks': 'Hide subtasks',
528
730
  'board.showSubtasks': 'Show subtasks',
529
731
  'board.subtaskFilterHint': 'The board shows parent tasks only; a text or label filter reveals the subtasks automatically.',
530
732
  }
531
733
 
734
+ /** The dictionary key union. */
735
+
532
736
  /** The dictionary key union. */
533
737
  export type TaskBoardKey = keyof typeof zh
534
738
 
@@ -16,12 +16,15 @@
16
16
  *
17
17
  * @module @linxin666/dsh-client-ui-task-board/client
18
18
  */
19
+ import { useMemo } from 'react'
20
+ import type { ReactNode } from 'react'
19
21
  import type { Context as ClientContext } from '@deepseek-ai/cordis'
20
22
  import type {} from '@deepseek-ai/dsh-client-ui-slots'
21
23
  import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
22
24
  import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
23
25
  import { TASK_BOARD_PANEL_ID, type BoardController } from '../core/controller.ts'
24
26
  import { TaskBoard } from './board/TaskBoard.tsx'
27
+ import { seatsFromRenderSlot, TaskBoardSeatsProvider, type SlotRenderBinding } from './seats.tsx'
25
28
  import { t } from './locales.ts'
26
29
  import css from './board.module.css'
27
30
 
@@ -72,10 +75,17 @@ export function TaskBoardPanelIcon({ size }: { size: number; active: boolean }):
72
75
  * @param props - the framework main-slot share plus this entry's injected face.
73
76
  * @returns the board page.
74
77
  */
75
- export function TaskBoardPanel({ controller }: { controller: BoardController }): React.ReactElement {
78
+ export function TaskBoardPanel({ controller, renderSlot }: {
79
+ controller: BoardController
80
+ /** Child-slot renderer the registration declares (detail + card decoration). */
81
+ renderSlot?: SlotRenderBinding
82
+ }): ReactNode {
83
+ const seats = useMemo(() => seatsFromRenderSlot(renderSlot), [renderSlot])
76
84
  return (
77
85
  <div className={css.panel} data-dsh-taskboard-view="" data-dsh-plugin="task-board">
78
- <TaskBoard controller={controller} />
86
+ <TaskBoardSeatsProvider value={seats}>
87
+ <TaskBoard controller={controller} />
88
+ </TaskBoardSeatsProvider>
79
89
  </div>
80
90
  )
81
91
  }
@@ -115,6 +125,12 @@ export function registerTaskBoardPanel(ctx: ClientContext, controller: BoardCont
115
125
  disposers.push(slots.inject('main', () => slots.register({
116
126
  name: 'main',
117
127
  key: TASK_BOARD_PANEL_ID,
128
+ // The board declares the provider child seats here; a provider registers
129
+ // into them and this entry renders them through the injected renderSlot.
130
+ children: {
131
+ 'task-board.detail.section': { kind: 'list', scope: 'root' },
132
+ 'task-board.card.decoration': { kind: 'list', scope: 'root' },
133
+ },
118
134
  inject: (): PanelFace => ({ controller }),
119
135
  }, TaskBoardPanel as never)))
120
136
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The board's child seats, threaded from the slot registration that declares
3
+ * them down to the components that consume them.
4
+ *
5
+ * The framework hands the declaring component a `renderSlot` binding; the board
6
+ * wraps that binding in a React context so a provider's contribution can render
7
+ * deep inside TaskDetail, the card, or the settings card without those
8
+ * components knowing which providers exist.
9
+ *
10
+ * @module dsh-task-board/client/seats
11
+ */
12
+ import { createContext, useContext } from 'react'
13
+ import type { ReactNode } from 'react'
14
+ import {
15
+ TASK_BOARD_CARD_DECORATION,
16
+ TASK_BOARD_DETAIL_SECTION,
17
+ TASK_BOARD_SETTINGS_SECTION,
18
+ type TaskBoardCardDecorationProps,
19
+ type TaskBoardDetailSectionProps,
20
+ type TaskBoardSettingsSectionProps,
21
+ } from '../core/extension.ts'
22
+
23
+ /** The three provider seats, adapted to plain render functions. */
24
+ export interface TaskBoardSeats {
25
+ detailSection: (props: TaskBoardDetailSectionProps) => ReactNode
26
+ settingsSection: (props: TaskBoardSettingsSectionProps) => ReactNode
27
+ cardDecoration: (props: TaskBoardCardDecorationProps) => ReactNode
28
+ }
29
+
30
+ /** Nothing registered: every seat renders empty. */
31
+ export const EMPTY_TASK_BOARD_SEATS: TaskBoardSeats = {
32
+ detailSection: () => null,
33
+ settingsSection: () => null,
34
+ cardDecoration: () => null,
35
+ }
36
+
37
+ /** The framework `renderSlot` binding shape (key + owner props share). */
38
+ export type SlotRenderBinding = (key: string, owner: Record<string, unknown>) => ReactNode
39
+
40
+ /** Adapt one framework renderSlot binding to the board's seat interface. */
41
+ export function seatsFromRenderSlot(renderSlot: SlotRenderBinding | undefined): TaskBoardSeats {
42
+ if (renderSlot === undefined) return EMPTY_TASK_BOARD_SEATS
43
+ return {
44
+ detailSection: props => renderSlot(TASK_BOARD_DETAIL_SECTION, props as unknown as Record<string, unknown>),
45
+ settingsSection: props => renderSlot(TASK_BOARD_SETTINGS_SECTION, props as unknown as Record<string, unknown>),
46
+ cardDecoration: props => renderSlot(TASK_BOARD_CARD_DECORATION, props as unknown as Record<string, unknown>),
47
+ }
48
+ }
49
+
50
+ const TaskBoardSeatsContext = createContext<TaskBoardSeats>(EMPTY_TASK_BOARD_SEATS)
51
+
52
+ /** Provider the board's registered components wrap their subtree in. */
53
+ export const TaskBoardSeatsProvider = TaskBoardSeatsContext.Provider
54
+
55
+ /** Read the current seat set (empty when no board registration is above). */
56
+ export function useTaskBoardSeats(): TaskBoardSeats {
57
+ return useContext(TaskBoardSeatsContext)
58
+ }
@@ -0,0 +1,94 @@
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 {
14
+ TaskBoardClientFace,
15
+ TaskBoardClientMirror,
16
+ TaskBoardExtensionActionRequest,
17
+ TaskBoardVisibilityPredicate,
18
+ } from '../core/extension.ts'
19
+ import type { TaskRecord } from '../core/tasks.ts'
20
+
21
+ export class TaskBoardClientService implements TaskBoardClientFace {
22
+ private controller: BoardController | undefined
23
+ private enabled = false
24
+ private unsubscribeController: (() => void) | undefined
25
+ /** Predicates registered before the controller exists, replayed on attach. */
26
+ private readonly pendingVisibility: TaskBoardVisibilityPredicate[] = []
27
+ private readonly listeners = new Set<(mirror: TaskBoardClientMirror) => void>()
28
+
29
+ /** Bind the live board controller (called when the UI mounts). */
30
+ attach(controller: BoardController): void {
31
+ if (this.controller === controller) return
32
+ this.detach()
33
+ this.controller = controller
34
+ this.unsubscribeController = controller.subscribe(() => { this.notify() })
35
+ for (const predicate of this.pendingVisibility.splice(0)) controller.registerVisibility(predicate)
36
+ this.notify()
37
+ }
38
+
39
+ /** Release the current controller (called when the UI unmounts). */
40
+ detach(): void {
41
+ if (this.controller === undefined) return
42
+ this.unsubscribeController?.()
43
+ this.unsubscribeController = undefined
44
+ this.controller = undefined
45
+ this.notify()
46
+ }
47
+
48
+ /** Apply the board's master switch. */
49
+ setEnabled(enabled: boolean): void {
50
+ if (this.enabled === enabled) return
51
+ this.enabled = enabled
52
+ this.notify()
53
+ }
54
+
55
+ dispatch(request: TaskBoardExtensionActionRequest): Promise<boolean> {
56
+ if (this.controller === undefined) return Promise.resolve(false)
57
+ return this.controller.dispatchExtension(request)
58
+ }
59
+
60
+ registerVisibility(predicate: TaskBoardVisibilityPredicate): () => void {
61
+ if (this.controller !== undefined) return this.controller.registerVisibility(predicate)
62
+ this.pendingVisibility.push(predicate)
63
+ return () => {
64
+ const index = this.pendingVisibility.indexOf(predicate)
65
+ if (index !== -1) this.pendingVisibility.splice(index, 1)
66
+ }
67
+ }
68
+
69
+ subscribe(callback: (mirror: TaskBoardClientMirror) => void): () => void {
70
+ this.listeners.add(callback)
71
+ return () => { this.listeners.delete(callback) }
72
+ }
73
+
74
+ snapshot(): TaskBoardClientMirror {
75
+ const state = this.controller?.getSnapshot()
76
+ return {
77
+ enabled: this.enabled,
78
+ tasks: (state?.tasks ?? []) as readonly TaskRecord[],
79
+ extensions: state?.extensions ?? {},
80
+ }
81
+ }
82
+
83
+ private notify(): void {
84
+ const mirror = this.snapshot()
85
+ for (const listener of [...this.listeners]) {
86
+ try {
87
+ listener(mirror)
88
+ } catch (error) {
89
+ // A provider's mirror callback must never break the board.
90
+ console.error('[dsh-task-board] extension mirror callback failed', error)
91
+ }
92
+ }
93
+ }
94
+ }