@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,331 @@
1
+ /**
2
+ * Goal acceptance gate: the board's evaluator-backed certification of a goal
3
+ * completion claim.
4
+ *
5
+ * The gate sits on the official tool pre-execute lifecycle and intercepts the
6
+ * one call that ends a goal run — `update_goal` with `action: 'complete'`
7
+ * — BEFORE it takes effect. This is why the board does not ask the agent to
8
+ * verify itself: the tool call that finishes the goal is refused until a
9
+ * matching acceptance pass record exists, so no prompt wording and no
10
+ * self-report can route around it.
11
+ *
12
+ * Semantics owned here:
13
+ * - one acceptance cycle per EXECUTION, spent at most twice: the first failure
14
+ * returns its findings to the fixing agent, the second ends the cycle;
15
+ * - a repeated completion call during a repair, a new automatic goal round, a
16
+ * plugin reload and a Host restart all reuse the same cycle rather than
17
+ * resetting it (the budget is persisted on the execution record);
18
+ * - an anomaly (timeout, authentication, unparseable answer, an unusable judge
19
+ * route) is recorded separately from a quality verdict and is never treated
20
+ * as one;
21
+ * - concurrent completion calls for one execution share one acceptance.
22
+ */
23
+ import type { PreToolDecision } from '@deepseek-ai/dsh-tools'
24
+ import type { LlmRuntime } from '@deepseek-ai/dsh-llm'
25
+ import {
26
+ MAX_EXCEPTION_ATTEMPTS,
27
+ MAX_QUALITY_ATTEMPTS,
28
+ exceptionAttempts,
29
+ hasExceptionBudget,
30
+ finalFailureReason,
31
+ hasQualityBudget,
32
+ passedAttempt,
33
+ qualityAttempts,
34
+ qualityFeedback,
35
+ type ExecutionVerification,
36
+ type VerificationAttempt,
37
+ } from '../core/verification.ts'
38
+ import { collectEvidence, evidenceEvents, runAcceptance } from './verification-runner.ts'
39
+ import { renderWorkspaceEvidence, type WorkspaceChangeSource } from './workspace-evidence.ts'
40
+ import type { HostTaskLedger } from '../host-ledger.ts'
41
+ import type { ExecutionRecord, TaskRecord } from '../core/tasks.ts'
42
+
43
+ /** The subset of the official goal service this gate drives. */
44
+ export interface GoalFace {
45
+ get(agent: unknown): { id: string; revision: number; objective: string } | undefined
46
+ block(agent: unknown, ref: { id: string; revision: number }, reason: { code: string; message: string }): unknown
47
+ }
48
+
49
+ /** Everything the gate needs from its owner. */
50
+ export interface GoalVerificationGateDeps {
51
+ ledger: HostTaskLedger
52
+ /** Resolve the optional model runtime lazily (it may activate later). */
53
+ llm?: () => LlmRuntime | undefined
54
+ /** Resolve the optional goal service lazily. */
55
+ goals?: () => GoalFace | undefined
56
+ /**
57
+ * Resolve the optional host change service lazily. Absent (or a deployment
58
+ * that serves none) simply means the judge sees the trajectory alone.
59
+ */
60
+ workspaceChanges?: () => WorkspaceChangeSource | undefined
61
+ logger: { warn(message: string, ...rest: unknown[]): void }
62
+ now?: () => number
63
+ }
64
+
65
+ /** The shape of one tool pre-execution the gate inspects. */
66
+ export interface GateExecution {
67
+ name: string
68
+ arguments: unknown
69
+ agent?: unknown
70
+ signal: AbortSignal
71
+ }
72
+
73
+ /** Structured denial detail, mirroring the host's ToolErrorInfo. */
74
+ interface GateDenial {
75
+ name: string
76
+ code: string
77
+ reason: string
78
+ }
79
+
80
+ /** Build the model-facing denial decision with its structured detail. */
81
+ function deny(reason: string, code: string, detail?: string): PreToolDecision {
82
+ const info: GateDenial = { name: 'TaskBoardGoalVerification', code, reason: detail ?? reason }
83
+ return { kind: 'deny', reason, info } as unknown as PreToolDecision
84
+ }
85
+
86
+ /** Read `action` out of the tool arguments. */
87
+ function actionOf(args: unknown): string | undefined {
88
+ if (typeof args !== 'object' || args === null) return undefined
89
+ const action = (args as Record<string, unknown>).action
90
+ return typeof action === 'string' ? action : undefined
91
+ }
92
+
93
+ /** Bound one message so a judge error cannot flood the tool result. */
94
+ function bounded(text: string, max = 4_000): string {
95
+ return text.length <= max ? text : text.slice(0, max) + '…'
96
+ }
97
+
98
+ /**
99
+ * Create the goal acceptance gate.
100
+ * @param deps - the ledger, the model runtime, the goal service and the settings.
101
+ * @returns the `tools/pre-execute` listener body (without the waterfall next()).
102
+ */
103
+ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exec: GateExecution) => Promise<PreToolDecision | undefined> {
104
+ const now = deps.now ?? Date.now
105
+ /** One acceptance per execution, shared by concurrent completion calls. */
106
+ const inFlight = new Map<string, Promise<PreToolDecision>>()
107
+
108
+ /** Persist one execution's acceptance block. */
109
+ const write = (taskId: string, executionId: string, verification: ExecutionVerification): void => {
110
+ deps.ledger.setVerification(taskId, executionId, verification)
111
+ }
112
+
113
+ /** Best-effort: stop the goal so an exhausted cycle cannot burn more rounds. */
114
+ const blockGoal = (agent: unknown, message: string): void => {
115
+ const goals = deps.goals?.()
116
+ if (goals === undefined) return
117
+ try {
118
+ const view = goals.get(agent)
119
+ if (view === undefined) return
120
+ goals.block(agent, { id: view.id, revision: view.revision }, { code: 'task-board-verification', message })
121
+ } catch (error) {
122
+ deps.logger.warn('[dsh-task-board] could not block the goal after a failed acceptance', error)
123
+ }
124
+ }
125
+
126
+ /** Record the final failure of one cycle: the report stays, the card must fail. */
127
+ const finalize = (
128
+ taskId: string,
129
+ executionId: string,
130
+ verification: ExecutionVerification,
131
+ reason: string,
132
+ agent: unknown,
133
+ ): PreToolDecision => {
134
+ write(taskId, executionId, { ...verification, inFlight: undefined, failedReason: reason, failedAt: now() })
135
+ blockGoal(agent, reason)
136
+ return deny(
137
+ bounded('[任务看板 · goal 验收判定失败] ' + reason + '\n本次执行不会完成;请如实向用户报告验收未通过、已记录的分数与问题,不要再尝试标记完成。'),
138
+ 'TASK_BOARD_VERIFICATION_FAILED',
139
+ reason,
140
+ )
141
+ }
142
+
143
+ /** Record one attempt and decide whether the completion claim may proceed. */
144
+ const settle = async (input: {
145
+ task: TaskRecord
146
+ execution: ExecutionRecord
147
+ verification: ExecutionVerification
148
+ agent: unknown
149
+ objective: string
150
+ }): Promise<PreToolDecision> => {
151
+ const { task, execution, verification, agent, objective } = input
152
+ const route = verification.contract.route
153
+ const baselines: ExecutionVerification = { ...verification, inFlight: true }
154
+ if (route === undefined) {
155
+ const attempt: VerificationAttempt = {
156
+ index: exceptionAttempts(verification).length + 1,
157
+ at: now(),
158
+ stage: 'exception',
159
+ passed: false,
160
+ score: 0,
161
+ baseline: 0,
162
+ criteria: [],
163
+ findings: [],
164
+ usage: { calls: 0, inputTokens: 0, outputTokens: 0, reasoningTokens: 0 },
165
+ evidence: { chars: 0, omittedCharacters: 0, entries: 0, hash: '' },
166
+ route: { provider: '', model: '' },
167
+ channel: 'explicit-tag',
168
+ rounds: 0,
169
+ error: '验收异常(裁判路由不可用): 宿主未提供模型目录,无法解析验收模型',
170
+ }
171
+ const attempts = [...verification.attempts, attempt]
172
+ const spend = { ...verification, attempts, inFlight: undefined }
173
+ if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
174
+ return finalize(task.id, execution.id, spend, '验收异常达到上限:' + attempt.error, agent)
175
+ }
176
+ write(task.id, execution.id, spend)
177
+ return deny(
178
+ bounded('[任务看板 · 验收异常] ' + attempt.error + '\n这是验收异常而非质量判定,本次未消耗质量验收额度(已用 ' + exceptionAttempts(spend).length + '/' + MAX_EXCEPTION_ATTEMPTS + ')。请修复环境后再次调用 update_goal(action: complete)。'),
179
+ 'TASK_BOARD_VERIFICATION_ANOMALY',
180
+ attempt.error,
181
+ )
182
+ }
183
+ write(task.id, execution.id, baselines)
184
+ const llm = deps.llm?.()
185
+ if (llm === undefined) {
186
+ const attempt: VerificationAttempt = {
187
+ index: exceptionAttempts(verification).length + 1,
188
+ at: now(),
189
+ stage: 'exception',
190
+ passed: false,
191
+ score: 0,
192
+ baseline: 0,
193
+ criteria: [],
194
+ findings: [],
195
+ usage: { calls: 0, inputTokens: 0, outputTokens: 0, reasoningTokens: 0 },
196
+ evidence: { chars: 0, omittedCharacters: 0, entries: 0, hash: '' },
197
+ route,
198
+ channel: 'explicit-tag',
199
+ rounds: 0,
200
+ error: '验收异常(裁判路由不可用): 本部署未提供 llm 服务',
201
+ }
202
+ const spend = { ...verification, attempts: [...verification.attempts, attempt], inFlight: undefined }
203
+ if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
204
+ return finalize(task.id, execution.id, spend, '验收异常达到上限:' + attempt.error, agent)
205
+ }
206
+ write(task.id, execution.id, spend)
207
+ return deny(bounded('[任务看板 · 验收异常] ' + attempt.error), 'TASK_BOARD_VERIFICATION_ANOMALY', attempt.error)
208
+ }
209
+ const session = (agent as { session?: unknown }).session
210
+ const evidence = collectEvidence(session, objective, execution.startedAt)
211
+ const acceptance = AbortSignal.timeout(600_000)
212
+ // The host's own record of what changed on disk is offered next to the
213
+ // trajectory; an unavailable service or a failed read degrades to the
214
+ // trajectory alone instead of failing an acceptance already under way.
215
+ const source = deps.workspaceChanges?.()
216
+ const workspace = source === undefined
217
+ ? { text: '', files: 0 }
218
+ : await renderWorkspaceEvidence(
219
+ evidenceEvents(session, execution.startedAt),
220
+ String((agent as { id?: unknown }).id ?? ''),
221
+ source,
222
+ { signal: acceptance, warn: message => { deps.logger.warn(message) } },
223
+ )
224
+ const result = await runAcceptance({
225
+ llm,
226
+ route,
227
+ evidence,
228
+ threshold: verification.contract.threshold,
229
+ index: qualityAttempts(verification).length + 1,
230
+ context: workspace.text === '' ? undefined : workspace.text,
231
+ workspaceFiles: workspace.files,
232
+ signal: acceptance,
233
+ now,
234
+ })
235
+ const attempt = result.attempt
236
+ const attempts = [...verification.attempts, attempt]
237
+ const spend: ExecutionVerification = { ...verification, attempts, inFlight: undefined }
238
+ if (attempt.stage === 'exception') {
239
+ if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
240
+ return finalize(task.id, execution.id, spend, '验收异常达到上限:' + (attempt.error ?? 'unknown'), agent)
241
+ }
242
+ write(task.id, execution.id, spend)
243
+ return deny(
244
+ bounded('[任务看板 · 验收异常] ' + (attempt.error ?? 'unknown') + '\n这是验收异常而非质量判定,本次未消耗质量验收额度(已用 ' + exceptionAttempts(spend).length + '/' + MAX_EXCEPTION_ATTEMPTS + ')。请修复环境后再次调用 update_goal(action: complete)。'),
245
+ 'TASK_BOARD_VERIFICATION_ANOMALY',
246
+ attempt.error,
247
+ )
248
+ }
249
+ if (attempt.passed) {
250
+ write(task.id, execution.id, spend)
251
+ return { kind: 'allow' }
252
+ }
253
+ const used = qualityAttempts(spend).length
254
+ if (used >= MAX_QUALITY_ATTEMPTS) {
255
+ return finalize(task.id, execution.id, spend, finalFailureReason(attempt, used, verification.contract.threshold), agent)
256
+ }
257
+ write(task.id, execution.id, spend)
258
+ return deny(bounded(qualityFeedback(attempt, MAX_QUALITY_ATTEMPTS - used)), 'TASK_BOARD_VERIFICATION_FAILED', attempt.findings.join('\n'))
259
+ }
260
+
261
+ /** Evaluate one tool call; undefined means "not this gate's business". */
262
+ const evaluate = async (exec: GateExecution): Promise<PreToolDecision | undefined> => {
263
+ const binding = (() => {
264
+ const agent = exec.agent as { id?: unknown } | undefined
265
+ const sessionId = agent?.id === undefined ? '' : String(agent.id)
266
+ return sessionId === '' ? undefined : deps.ledger.findOpenExecutionBySession(sessionId)
267
+ })()
268
+ if (binding === undefined) return undefined
269
+ const verification = binding.execution.verification
270
+ if (verification === undefined || verification.contract.enabled === false) return undefined
271
+ // Only a goal run of the board's own execution is gated: a teammate
272
+ // execution is certified through its Lead's aggregated evidence.
273
+ if (verification.applicability !== 'enforced') return undefined
274
+ if (passedAttempt(verification) !== undefined) return { kind: 'allow' }
275
+ if (verification.failedReason !== undefined) {
276
+ return deny(
277
+ bounded('[任务看板 · goal 验收判定失败] ' + verification.failedReason + '\n本次执行已被判失败,不会再运行新的验收。请如实报告结果。'),
278
+ 'TASK_BOARD_VERIFICATION_FAILED',
279
+ verification.failedReason,
280
+ )
281
+ }
282
+ if (!hasQualityBudget(verification) && !hasExceptionBudget(verification)) {
283
+ return finalize(binding.task.id, binding.execution.id, verification, 'goal 验收额度已耗尽,本次 execution 判失败。', exec.agent)
284
+ }
285
+ const key = binding.execution.id
286
+ const running = inFlight.get(key)
287
+ if (running !== undefined) return await running
288
+ const job = (async (): Promise<PreToolDecision> => {
289
+ const view = (() => {
290
+ const goals = deps.goals?.()
291
+ try {
292
+ return goals?.get(exec.agent)
293
+ } catch {
294
+ return undefined
295
+ }
296
+ })()
297
+ const objective = view?.objective !== undefined && view.objective.trim() !== '' ? view.objective : taskPrompt(binding.task)
298
+ return await settle({ task: binding.task, execution: binding.execution, verification, agent: exec.agent, objective })
299
+ })()
300
+ inFlight.set(key, job)
301
+ try {
302
+ return await job
303
+ } finally {
304
+ inFlight.delete(key)
305
+ }
306
+ }
307
+
308
+ return async (exec: GateExecution): Promise<PreToolDecision | undefined> => {
309
+ if (exec.name !== 'update_goal') return undefined
310
+ if (actionOf(exec.arguments) !== 'complete') return undefined
311
+ if (exec.agent === undefined) return undefined
312
+ // A call the caller already cancelled is not a policy refusal: let the host
313
+ // produce its own cancellation result.
314
+ if (exec.signal.aborted) return undefined
315
+ try {
316
+ return await evaluate(exec)
317
+ } catch (error) {
318
+ deps.logger.warn('[dsh-task-board] goal acceptance gate failed; the completion claim is refused (fail closed)', error)
319
+ return deny(
320
+ bounded('[任务看板 · 验收异常] 验收流程自身出错,本次完成声明已被拒绝:' + (error instanceof Error ? error.message : String(error))),
321
+ 'TASK_BOARD_VERIFICATION_ANOMALY',
322
+ error instanceof Error ? error.message : String(error),
323
+ )
324
+ }
325
+ }
326
+ }
327
+
328
+ /** The bare execution prompt, used when the goal service carries no objective. */
329
+ function taskPrompt(task: TaskRecord): string {
330
+ return task.prompt.trim() !== '' ? task.prompt : task.title
331
+ }